From 5606cef2243d99b72af8461cdfc62fa792a38fa6 Mon Sep 17 00:00:00 2001
From: danielgap
Date: Thu, 29 Oct 2026 23:29:18 +0100
Subject: [PATCH 1/3] =?UTF-8?q?feat(i18n):=20migrar=20CliConnect=20y=20Acc?=
=?UTF-8?q?ountEditor=20a=20los=20cat=C3=A1logos=20ES/EN=20=E2=80=94=20T2c?=
=?UTF-8?q?=20slice=202=20C1a=20(#66)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
CliConnect (vía CliConnectView, extraída verbatim para SSR) y AccountEditor
pasan sus textos a settings.providers.{cliConnect,accountEditor}.* con 4 claves
compartidas; con es todo se ve igual. CLI_INSTALL gana whereKey para que
CliMissing traduzca por clave y no comparando 'una terminal' (nota 1 de #204).
Marcas, rutas, comandos, planes con cifra y rótulos de UIs de terceros quedan
literales.
---
src/Settings-accounts-i18n.test.tsx | 476 ++++++++++++++++++++++++++++
src/Settings.tsx | 205 ++++++++----
src/i18n/en.ts | 62 ++++
src/i18n/es.ts | 69 ++++
src/lib/accounts.ts | 13 +-
5 files changed, 755 insertions(+), 70 deletions(-)
create mode 100644 src/Settings-accounts-i18n.test.tsx
diff --git a/src/Settings-accounts-i18n.test.tsx b/src/Settings-accounts-i18n.test.tsx
new file mode 100644
index 0000000..f3116d5
--- /dev/null
+++ b/src/Settings-accounts-i18n.test.tsx
@@ -0,0 +1,476 @@
+// Corte visible de i18n para los pasos de conexión por CLI (`CliConnect`) y el
+// editor de cuentas adicionales (`AccountEditor`) de Ajustes › Proveedores (#66,
+// T2c slice 2 C1a), 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. También cubre `whereKey` de `CLI_INSTALL`: el
+// aviso de `CliMissing` se traduce por clave, no comparando un texto español.
+import { renderToStaticMarkup } from 'react-dom/server';
+import { describe, expect, it } from 'vitest';
+import { CLI_INSTALL, defaultSessionDir } from './lib/accounts';
+import type { CodexInstallController } from './lib/codex-install';
+import type { Account, AccountProvider, Locale } from './lib/types';
+import { AccountEditor, CliConnectView, CliMissing } 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);
+};
+
+const health = (locale: Locale, over: { dirty?: boolean } = {}) => ({
+ saved: null,
+ secretsSig: '',
+ dirty: over.dirty ?? false,
+ hidden: false,
+ saving: false,
+ onSave: () => Promise.resolve(true),
+ locale,
+});
+
+// `CodexInstall` solo se pinta con `codex` y el CLI ausente; aquí no se usa.
+const codexInstall = {} as CodexInstallController;
+
+const connect = (
+ locale: Locale,
+ provider: 'claude' | 'gemini',
+ over: { cliMissing?: boolean; persisted?: boolean; extra?: boolean; dirty?: boolean; connected?: boolean; loginError?: string | null } = {},
+) =>
+ renderToStaticMarkup(
+ ,
+ );
+
+describe('CliConnect steps (SSR, i18n)', () => {
+ const states = (locale: Locale) => ({
+ missingClaude: connect(locale, 'claude', { cliMissing: true }),
+ claudeDirty: connect(locale, 'claude', { persisted: true, dirty: true }),
+ claudeSaved: connect(locale, 'claude', { persisted: true }),
+ geminiExtra: connect(locale, 'gemini', { extra: true, persisted: true }),
+ geminiConnected: connect(locale, 'gemini', { persisted: true, connected: true, extra: true }),
+ failed: connect(locale, 'claude', { persisted: true, loginError: 'boom' }),
+ });
+
+ it('renders the Spanish literals copied verbatim from the current code', () => {
+ const s = states('es');
+ expectAll(
+ s.missingClaude,
+ [
+ 'Instala Claude Code',
+ 'Hace falta para iniciar sesión y para renovarla cuando caduque, aunque ahora funcione.',
+ 'Falta Claude Code en este equipo.',
+ 'Inicia sesión',
+ 'Se abre una terminal con claude (si no te lo pide, escribe /login). El navegador hace el login oficial y el CLI guarda la sesión, que Quorb solo lee.',
+ 'Guardar e iniciar sesión',
+ 'Comprobación',
+ 'Se hace sola en cuanto guardes e inicies sesión.',
+ ],
+ 'es',
+ );
+ expect(s.claudeDirty).toContain('Guardar e iniciar sesión');
+ expectAll(s.claudeSaved, ['>Iniciar sesión', 'Comprobando la conexión…'], 'es');
+ expect(s.claudeSaved).not.toContain('Guardar e iniciar sesión');
+ expectAll(
+ s.geminiExtra,
+ [
+ 'Inicia sesión con la otra cuenta',
+ 'Se abre una terminal con gemini (elige «Login with Google»). El navegador hace el login oficial y el CLI guarda la sesión, que Quorb solo lee.',
+ ' Entra con la cuenta que quieres añadir, no con la principal: si el navegador ya tiene abierta la principal, usa una ventana privada.',
+ ],
+ 'es',
+ );
+ expectAll(
+ s.geminiConnected,
+ [
+ 'Fichero de sesión encontrado',
+ 'que Quorb solo lee. El fichero existe, pero no confirma que la sesión siga siendo válida: míralo en la comprobación de abajo.',
+ '>Volver a abrir el login',
+ ],
+ 'es',
+ );
+ expectAll(s.failed, ['boom'], 'es');
+ expectNone(s.claudeSaved, ['Fichero de sesión encontrado', 'Entra con la cuenta'], 'es');
+ });
+
+ it('renders the English texts when the locale is en, with no Spanish left', () => {
+ const s = states('en');
+ expectAll(
+ s.missingClaude,
+ [
+ 'Install Claude Code',
+ 'It is needed to sign in and to renew the session when it expires, even if it works now.',
+ 'Claude Code is missing on this computer.',
+ 'Sign in',
+ 'A terminal opens with claude (if it does not ask, type /login). The browser does the official login and the CLI stores the session, which Quorb only reads.',
+ 'Save and sign in',
+ 'Check',
+ 'It runs on its own as soon as you save and sign in.',
+ ],
+ 'en',
+ );
+ expect(s.claudeDirty).toContain('Save and sign in');
+ expectAll(s.claudeSaved, ['>Sign in', 'Checking the connection…'], 'en');
+ expectAll(
+ s.geminiExtra,
+ [
+ 'Sign in with the other account',
+ 'A terminal opens with gemini (choose “Login with Google”).',
+ ' 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.',
+ ],
+ 'en',
+ );
+ expectAll(
+ s.geminiConnected,
+ [
+ 'Session file found',
+ 'which Quorb only reads. The file exists, but it does not confirm the session is still valid: check the verification below.',
+ '>Open the login again',
+ ],
+ 'en',
+ );
+ expectNone(
+ Object.values(s).join('\n'),
+ [
+ 'Instala',
+ 'Hace falta',
+ 'Inicia sesión',
+ 'Iniciar sesión',
+ 'Se abre una terminal',
+ 'si no te lo pide',
+ 'elige',
+ 'Fichero',
+ 'El fichero existe',
+ 'Entra con la cuenta',
+ 'Volver a abrir',
+ 'Guardar e iniciar',
+ 'Comprobación',
+ 'Se hace sola',
+ 'su CLI',
+ ],
+ 'en',
+ );
+ });
+});
+
+describe('AccountEditor (SSR, i18n)', () => {
+ const account = (provider: AccountProvider, over: Partial = {}): Account =>
+ ({
+ id: `${provider}:b`,
+ provider,
+ label: 'Mi cuenta',
+ enabled: true,
+ configDir: null,
+ username: 'egdev6',
+ plan: '',
+ customAllowance: null,
+ monthlyBudgetUsd: null,
+ ...over,
+ }) as Account;
+
+ const editor = (locale: Locale, a: Account, persisted = false) =>
+ renderToStaticMarkup(
+ ,
+ );
+
+ const claude = account('claude');
+ const dir = defaultSessionDir(claude, null);
+
+ it('renders the Spanish literals copied verbatim from the current code', () => {
+ const base = editor('es', claude);
+ expectAll(
+ base,
+ [
+ '>Cuenta adicional
',
+ '
Mi cuenta
',
+ 'Nombre',
+ 'Se ve en la tira junto al proveedor.',
+ 'Mostrar en el widget',
+ 'aria-label="Cómo conectar esta cuenta"',
+ 'Ponle nombre',
+ 'Arriba, para distinguirla de la principal en la tira (por ejemplo, «Trabajo»).',
+ 'Comprobación',
+ 'Avanzado · carpeta de sesión',
+ 'Eliminar esta cuenta',
+ 'Al guardar se borran también sus claves del almacén seguro del sistema.',
+ ],
+ 'es',
+ );
+ expectAll(
+ base,
+ [
+ 'Cada cuenta guarda su sesión en su propia carpeta, así no toca la principal: el login se abre con CLAUDE_CONFIG_DIR apuntando a ella. Si la comprobación pide renovar la sesión, «Volver a abrir el login» abre de nuevo el CLI.',
+ 'Carpeta de sesión',
+ `Vacío = ${dir}. Puedes usar una que ya tengas. Quorb lee .credentials.json dentro de ella.`,
+ ],
+ 'es',
+ );
+ // Cuenta sin guardar: el paso de comprobación pide guardar; guardada, comprueba.
+ expectAll(editor('es', account('openrouter')), ['Guarda la cuenta para comprobar que funciona.', 'Guardar y comprobar'], 'es');
+ expect(editor('es', account('openrouter'), true)).not.toContain('Guarda la cuenta para comprobar');
+
+ expectAll(
+ editor('es', account('cursor')),
+ [
+ 'Pega el token de esta cuenta',
+ 'La app de Cursor guarda una sola sesión (la principal). Entra en cursor.com con esta cuenta y copia la cookie WorkosCursorSessionToken.',
+ 'Token de la cuenta',
+ ],
+ 'es',
+ );
+ const copilot = editor('es', account('copilot', { plan: 'custom' }));
+ expectAll(
+ copilot,
+ [
+ 'Usuario y plan de GitHub',
+ 'Usuario de GitHub',
+ 'Plan',
+ '>Free · 50<',
+ '>Pro · 300<',
+ '>Pro+ · 1500<',
+ '>Business · 300<',
+ '>Enterprise · 1000<',
+ '>Otra cantidad<',
+ 'Peticiones premium incluidas al mes',
+ 'Token de solo lectura',
+ 'Token de GitHub de esta cuenta (fine-grained)',
+ 'Créalo con esta cuenta en GitHub › Fine-grained tokens con el permiso «Plan» (solo lectura).',
+ ],
+ 'es',
+ );
+ expectAll(
+ editor('es', account('nan')),
+ [
+ 'Conecta la cuenta o pega una API key',
+ 'API key (alternativa)',
+ 'Plan',
+ 'La URL base y el resto de ajustes avanzados son los de la cuenta principal.',
+ '>nan_community<',
+ '>nan_member<',
+ '>GLM Premium<',
+ ],
+ 'es',
+ );
+ expectAll(
+ editor('es', account('openrouter')),
+ ['API key', 'Puede ser de otra cuenta o de la misma con otro límite. openrouter.ai › Keys'],
+ 'es',
+ );
+ expectAll(editor('es', account('zai')), ['API key', 'Créala en z.ai › API Keys.'], 'es');
+ expectAll(
+ editor('es', account('opencode_go')),
+ ['API key de OpenCode Go', 'Créala en OpenCode › Console.'],
+ 'es',
+ );
+ expectAll(
+ editor('es', account('anthropic_api')),
+ [
+ 'Admin key de esta organización',
+ 'Admin key',
+ 'Cada organización tiene su propia Admin key.',
+ 'Presupuesto mensual (opcional)',
+ 'USD al mes',
+ ],
+ 'es',
+ );
+ });
+
+ it('renders the English texts when the locale is en, with no Spanish left', () => {
+ const base = editor('en', claude);
+ expectAll(
+ base,
+ [
+ '>Additional account',
+ '
Mi cuenta
',
+ 'Name',
+ 'Shown on the strip next to the provider.',
+ 'Show in the widget',
+ 'aria-label="How to connect this account"',
+ 'Name it',
+ 'Above, to tell it apart from the main one on the strip (for example, “Work”).',
+ 'Check',
+ 'Advanced · session folder',
+ 'Remove this account',
+ 'Saving also deletes its keys from the system secure store.',
+ 'Each account keeps its session in its own folder, so it does not touch the main one: the login opens with CLAUDE_CONFIG_DIR pointing to it. If the check asks you to renew the session, “Open the login again” opens the CLI again.',
+ 'Session folder',
+ `Empty = ${dir}. You can use one you already have. Quorb reads .credentials.json inside it.`,
+ ],
+ 'en',
+ );
+ const openrouter = editor('en', account('openrouter'));
+ expectAll(openrouter, ['Save the account to check that it works.', 'Save and check'], 'en');
+
+ const cursor = editor('en', account('cursor'));
+ expectAll(
+ cursor,
+ [
+ 'Paste this account token',
+ 'The Cursor app stores a single session (the main one). Sign in at cursor.com with this account and copy the WorkosCursorSessionToken cookie.',
+ 'Account token',
+ ],
+ 'en',
+ );
+ const copilot = editor('en', account('copilot', { plan: 'custom' }));
+ expectAll(
+ copilot,
+ [
+ 'GitHub user and plan',
+ 'GitHub username',
+ 'Plan',
+ '>Free · 50<',
+ '>Pro · 300<',
+ '>Pro+ · 1500<',
+ '>Business · 300<',
+ '>Enterprise · 1000<',
+ '>Other amount<',
+ 'Premium requests included per month',
+ 'Read-only token',
+ 'GitHub token for this account (fine-grained)',
+ 'Create it with this account at GitHub › Fine-grained tokens with the “Plan” permission (read-only).',
+ ],
+ 'en',
+ );
+ const nan = editor('en', account('nan'));
+ expectAll(
+ nan,
+ [
+ 'Connect the account or paste an API key',
+ 'API key (alternative)',
+ 'Plan',
+ 'The base URL and the other advanced settings are those of the main account.',
+ '>nan_community<',
+ '>nan_member<',
+ '>GLM Premium<',
+ ],
+ 'en',
+ );
+ const zai = editor('en', account('zai'));
+ expectAll(openrouter, ['API key', 'It can be from another account or from the same one with a different limit. openrouter.ai › Keys'], 'en');
+ expectAll(zai, ['API key', 'Create it at z.ai › API Keys.'], 'en');
+ const opencode = editor('en', account('opencode_go'));
+ expectAll(opencode, ['OpenCode Go API key', 'Create it at OpenCode › Console.'], 'en');
+ const admin = editor('en', account('anthropic_api'));
+ expectAll(
+ admin,
+ [
+ 'Admin key for this organization',
+ 'Each organization has its own Admin key.',
+ 'Monthly budget (optional)',
+ 'USD per month',
+ ],
+ 'en',
+ );
+ // NanConnect y el resto de paneles siguen en español hasta C2 (fuera de este
+ // corte): solo se comprueba lo que pinta `AccountEditor` en sí.
+ expectNone(
+ [base, openrouter, cursor, copilot, nan, zai, opencode, admin].join('\n'),
+ [
+ 'Cuenta adicional',
+ 'Nombre',
+ 'Se ve en la tira',
+ 'Mostrar en el widget',
+ 'Cómo conectar',
+ 'Ponle nombre',
+ 'Trabajo',
+ 'Comprobación',
+ 'Guarda la cuenta',
+ 'Guardar y comprobar',
+ 'Avanzado',
+ 'Cada cuenta guarda',
+ 'Carpeta de sesión',
+ 'Vacío',
+ 'Puedes usar',
+ 'dentro de ella',
+ 'Eliminar esta cuenta',
+ 'Al guardar se borran',
+ 'Pega el token',
+ 'La app de Cursor',
+ 'Token de',
+ 'Usuario',
+ 'Otra cantidad',
+ 'Peticiones premium',
+ 'solo lectura',
+ 'Créalo',
+ 'Créala',
+ 'Conecta la cuenta',
+ '(alternativa)',
+ 'La URL base',
+ 'Puede ser de otra cuenta',
+ 'API key de OpenCode',
+ 'Admin key de esta',
+ 'Cada organización',
+ 'Presupuesto mensual',
+ 'USD al mes',
+ ],
+ 'en',
+ );
+ });
+});
+
+describe('CLI_INSTALL.whereKey (CliMissing, i18n)', () => {
+ const missing = (locale: Locale, provider: 'claude' | 'codex' | 'gemini') => renderToStaticMarkup();
+
+ it('translates the terminal phrase through whereKey for codex and gemini', () => {
+ for (const provider of ['codex', 'gemini'] as const) {
+ expect(CLI_INSTALL[provider]?.whereKey, provider).toBe('settings.providers.cliMissing.terminal');
+ expectAll(missing('es', provider), ['Instálalo desde una terminal (necesita Node.js);'], `es ${provider}`);
+ expectAll(missing('en', provider), ['Install it from a terminal (needs Node.js);'], `en ${provider}`);
+ expectNone(missing('en', provider), ['una terminal', 'Instálalo'], `en ${provider}`);
+ }
+ });
+
+ it('keeps PowerShell literal for claude, with no whereKey', () => {
+ expect(CLI_INSTALL.claude?.whereKey).toBeUndefined();
+ expect(CLI_INSTALL.claude?.where).toBe('PowerShell');
+ expectAll(missing('es', 'claude'), ['Instálalo desde PowerShell; Quorb'], 'es');
+ expectAll(missing('en', 'claude'), ['Install it from PowerShell; Quorb'], 'en');
+ });
+
+ it('does not depend on the Spanish text of the install data', () => {
+ const gemini = CLI_INSTALL.gemini as { where?: string };
+ const before = gemini.where;
+ gemini.where = 'otra cosa';
+ try {
+ expectAll(missing('en', 'gemini'), ['Install it from a terminal (needs Node.js);'], 'en');
+ expectNone(missing('en', 'gemini'), ['otra cosa'], 'en');
+ } finally {
+ if (before === undefined) delete gemini.where;
+ else gemini.where = before;
+ }
+ });
+});
diff --git a/src/Settings.tsx b/src/Settings.tsx
index ca15629..0152702 100644
--- a/src/Settings.tsx
+++ b/src/Settings.tsx
@@ -576,7 +576,7 @@ export function CliMissing({ provider, locale = 'es' }: { provider: AccountProvi
{t(locale, 'settings.providers.cliMissing.title', { name: how.name })}{' '}
{t(locale, 'settings.providers.cliMissing.body', {
- where: how.where === 'una terminal' ? t(locale, 'settings.providers.cliMissing.terminal') : how.where,
+ where: how.whereKey ? t(locale, how.whereKey) : how.where,
needs: how.needs ? t(locale, 'settings.providers.cliMissing.needs', { needs: how.needs }) : '',
})}
@@ -784,6 +784,7 @@ function useCliSession(id: string, active: boolean): boolean {
* adicional: instalar el CLI si falta, iniciar sesión y comprobar que funciona.
*/
function CliConnect({
+ locale,
id,
provider,
loginCommand,
@@ -794,6 +795,8 @@ function CliConnect({
codexInstall,
onCodexRecheck,
}: {
+ /** Idioma de los pasos. */
+ locale: Locale;
id: string;
provider: AccountProvider;
loginCommand: string;
@@ -829,28 +832,88 @@ function CliConnect({
setBusy(false);
}
};
+ return (
+
+ );
+}
+
+/** Vista de los pasos de conexión por CLI a partir de su estado. Extraída para
+ * poder renderizar en pruebas cada estado con el locale explícito. */
+export function CliConnectView({
+ locale,
+ id,
+ provider,
+ loginCommand,
+ cliMissing,
+ persisted,
+ extra,
+ health,
+ codexInstall,
+ onCodexRecheck,
+ connected,
+ busy,
+ loginError,
+ onConnect,
+}: {
+ locale: Locale;
+ id: string;
+ provider: AccountProvider;
+ loginCommand: string;
+ cliMissing: boolean;
+ persisted: boolean;
+ extra: boolean;
+ health: HealthProps;
+ codexInstall: CodexInstallController;
+ onCodexRecheck: () => void;
+ /** Hay fichero de sesión del CLI. */
+ connected: boolean;
+ /** Hay un login o guardado en curso. */
+ busy: boolean;
+ /** Diagnóstico del último intento de login que falló. */
+ loginError: string | null;
+ onConnect: () => void;
+}) {
return (
<>
{cliMissing && (
- Instala {CLI_INSTALL[provider]?.name ?? 'su CLI'}
- Hace falta para iniciar sesión y para renovarla cuando caduque, aunque ahora funcione.
- {provider === 'codex' ? : }
+ {t(locale, 'settings.providers.cliConnect.installTitle', { name: CLI_INSTALL[provider]?.name ?? t(locale, 'settings.providers.cliConnect.cliFallback') })}
+ {t(locale, 'settings.providers.cliConnect.installBody')}
+ {provider === 'codex' ? : }
)}
- {connected ? 'Fichero de sesión encontrado' : extra ? 'Inicia sesión con la otra cuenta' : 'Inicia sesión'}
+ {connected ? t(locale, 'settings.providers.cliConnect.sessionFound') : extra ? t(locale, 'settings.providers.cliConnect.loginOther') : t(locale, 'settings.providers.cliConnect.login')}
- Se abre una terminal con {loginCommand}
- {provider === 'claude' && <> (si no te lo pide, escribe /login)>}
- {provider === 'gemini' && <> (elige «Login with Google»)>}. El navegador hace el login oficial y el CLI guarda la sesión, que
- Quorb solo lee.
- {connected && ' El fichero existe, pero no confirma que la sesión siga siendo válida: míralo en la comprobación de abajo.'}
- {extra && ' Entra con la cuenta que quieres añadir, no con la principal: si el navegador ya tiene abierta la principal, usa una ventana privada.'}
+ {t(locale, 'settings.providers.cliConnect.opensTerminal')}{' '}
+ {loginCommand}
+ {provider === 'claude' && <>{t(locale, 'settings.providers.cliConnect.claudeLoginHint')} /login)>}
+ {provider === 'gemini' && <>{t(locale, 'settings.providers.cliConnect.geminiLoginHint')}>}{t(locale, 'settings.providers.cliConnect.browserLogin')}
+ {connected && t(locale, 'settings.providers.cliConnect.fileExists')}
+ {extra && t(locale, 'settings.providers.cliConnect.extraNote')}
-
{loginError && (
@@ -860,11 +923,11 @@ function CliConnect({
)}
{persisted ? (
-
+
) : (
- Comprobación
- Se hace sola en cuanto guardes e inicies sesión.
+ {t(locale, 'settings.providers.checkTitle')}
+ {t(locale, 'settings.providers.cliConnect.checkAuto')}
)}
>
@@ -872,7 +935,8 @@ function CliConnect({
}
/** Configuración de una cuenta adicional: nombre, si se muestra y cómo se conecta, en pasos. */
-function AccountEditor({
+export function AccountEditor({
+ locale,
account: a,
visible,
onVisibility,
@@ -887,6 +951,8 @@ function AccountEditor({
codexInstall,
onCodexRecheck,
}: {
+ /** Idioma del editor. */
+ locale: Locale;
account: Account;
visible: boolean;
onVisibility: (enabled: boolean) => void;
@@ -914,40 +980,40 @@ function AccountEditor({
) : (
- Comprobación
- Guarda la cuenta para comprobar que funciona.
+ {t(locale, 'settings.providers.checkTitle')}
+ {t(locale, 'settings.providers.accountEditor.checkSave')} void health.onSave()}>
- Guardar y comprobar
+ {t(locale, 'settings.providers.health.saveAndCheck')}
);
return (
-
+
-
+
onChange({ label: e.target.value })} />
-
+
-
+
- Ponle nombre
- Arriba, para distinguirla de la principal en la tira (por ejemplo, «Trabajo»).
+ {t(locale, 'settings.providers.accountEditor.nameStep')}
+ {t(locale, 'settings.providers.accountEditor.nameStepBody')}
- Pega el token de esta cuenta
- La app de Cursor guarda una sola sesión (la principal). Entra en cursor.com con esta cuenta y copia la cookie WorkosCursorSessionToken.
-
+ {t(locale, 'settings.providers.accountEditor.cursorTokenTitle')}
+ {t(locale, 'settings.providers.accountEditor.cursorTokenBody')}
+
- Token de solo lectura
+ {t(locale, 'settings.providers.accountEditor.readOnlyToken')}Créalo con esta cuenta en GitHub › Fine-grained tokens con el permiso «Plan» (solo lectura).>}
+ hint={<>{t(locale, 'settings.providers.accountEditor.githubTokenCreateIn')} GitHub › Fine-grained tokens {t(locale, 'settings.providers.accountEditor.githubTokenPermission')}>}
/>
- Conecta la cuenta o pega una API key
+ {t(locale, 'settings.providers.accountEditor.nanTitle')}
-
+
- Plan
-
+ {t(locale, 'settings.providers.planLabel')}
+
);
@@ -2242,6 +2311,7 @@ export function Settings() {
{selAccount && (
{info.note}
`.
+ cliConnect: {
+ installTitle: 'Instala {name}',
+ cliFallback: 'su CLI',
+ installBody: 'Hace falta para iniciar sesión y para renovarla cuando caduque, aunque ahora funcione.',
+ sessionFound: 'Fichero de sesión encontrado',
+ loginOther: 'Inicia sesión con la otra cuenta',
+ login: 'Inicia sesión',
+ opensTerminal: 'Se abre una terminal con',
+ claudeLoginHint: ' (si no te lo pide, escribe',
+ geminiLoginHint: ' (elige «Login with Google»)',
+ browserLogin: '. El navegador hace el login oficial y el CLI guarda la sesión, que Quorb solo lee.',
+ fileExists: ' El fichero existe, pero no confirma que la sesión siga siendo válida: míralo en la comprobación de abajo.',
+ extraNote: ' Entra con la cuenta que quieres añadir, no con la principal: si el navegador ya tiene abierta la principal, usa una ventana privada.',
+ reopenLogin: 'Volver a abrir el login',
+ signIn: 'Iniciar sesión',
+ saveAndSignIn: 'Guardar e iniciar sesión',
+ checkAuto: 'Se hace sola en cuanto guardes e inicies sesión.',
+ },
+ // Editor de una cuenta adicional (`AccountEditor`). Los nombres de plan con
+ // cifra (`Pro · 300`), las opciones de producto (`nan_community`, `GLM Premium`)
+ // y los textos de enlace de terceros no se traducen.
+ accountEditor: {
+ kicker: 'Cuenta adicional',
+ nameLabel: 'Nombre',
+ nameHint: 'Se ve en la tira junto al proveedor.',
+ stepsAria: 'Cómo conectar esta cuenta',
+ nameStep: 'Ponle nombre',
+ nameStepBody: 'Arriba, para distinguirla de la principal en la tira (por ejemplo, «Trabajo»).',
+ checkSave: 'Guarda la cuenta para comprobar que funciona.',
+ cursorTokenTitle: 'Pega el token de esta cuenta',
+ cursorTokenBody: 'La app de Cursor guarda una sola sesión (la principal). Entra en cursor.com con esta cuenta y copia la cookie WorkosCursorSessionToken.',
+ cursorTokenLabel: 'Token de la cuenta',
+ copilotUserPlan: 'Usuario y plan de GitHub',
+ githubUser: 'Usuario de GitHub',
+ planCustom: 'Otra cantidad',
+ customAllowance: 'Peticiones premium incluidas al mes',
+ readOnlyToken: 'Token de solo lectura',
+ githubTokenLabel: 'Token de GitHub de esta cuenta (fine-grained)',
+ githubTokenCreateIn: 'Créalo con esta cuenta en',
+ githubTokenPermission: 'con el permiso «Plan» (solo lectura).',
+ nanTitle: 'Conecta la cuenta o pega una API key',
+ nanApiKeyAlt: 'API key (alternativa)',
+ nanPlanHint: 'La URL base y el resto de ajustes avanzados son los de la cuenta principal.',
+ openrouterHint: 'Puede ser de otra cuenta o de la misma con otro límite.',
+ createIn: 'Créala en',
+ apiKeyOf: 'API key de {name}',
+ adminKeyTitle: 'Admin key de esta organización',
+ adminKey: 'Admin key',
+ adminKeyOwn: 'Cada organización tiene su propia',
+ budgetTitle: 'Presupuesto mensual (opcional)',
+ budgetLabel: 'USD al mes',
+ advanced: 'Avanzado · carpeta de sesión',
+ sessionNoteBefore: 'Cada cuenta guarda su sesión en su propia carpeta, así no toca la principal: el login se abre con',
+ sessionNoteAfter: 'apuntando a ella. Si la comprobación pide renovar la sesión, «{reopen}» abre de nuevo el CLI.',
+ sessionDir: 'Carpeta de sesión',
+ sessionDirEmpty: 'Vacío =',
+ sessionDirMiddle: '. Puedes usar una que ya tengas. Quorb lee',
+ sessionDirEnd: 'dentro de ella.',
+ remove: 'Eliminar esta cuenta',
+ removeHint: 'Al guardar se borran también sus claves del almacén seguro del sistema.',
+ },
},
},
// T2c slice 1 (#66): tira del widget y detalle de proveedor. Valores verbatim.
diff --git a/src/lib/accounts.ts b/src/lib/accounts.ts
index 4eb1a8d..6ae4303 100644
--- a/src/lib/accounts.ts
+++ b/src/lib/accounts.ts
@@ -1,6 +1,7 @@
// Cuentas adicionales de un proveedor (src-tauri/src/config.rs › Account).
import type { Account, AccountProvider, AppConfig, ScopedSecretKey, SecretKey, SecretStatus } from './types';
import { ownedHomeDir } from './app-identity';
+import type { MessageKey } from '../i18n';
/** Proveedor de un id de informe: `claude` o `claude:trabajo` → `claude`. */
export const providerOf = (id: string): string => id.split(':')[0];
@@ -17,11 +18,17 @@ export const SESSION_DIR: Partial> = {
+export const CLI_INSTALL: Partial> = {
claude: { name: 'Claude Code', cmd: 'irm https://claude.ai/install.ps1 | iex', where: 'PowerShell' },
- codex: { name: 'Codex CLI', cmd: 'npm install -g @openai/codex', where: 'una terminal', needs: 'Node.js' },
- gemini: { name: 'Gemini CLI', cmd: 'npm install -g @google/gemini-cli', where: 'una terminal', needs: 'Node.js' },
+ codex: { name: 'Codex CLI', cmd: 'npm install -g @openai/codex', whereKey: 'settings.providers.cliMissing.terminal', needs: 'Node.js' },
+ gemini: { name: 'Gemini CLI', cmd: 'npm install -g @google/gemini-cli', whereKey: 'settings.providers.cliMissing.terminal', needs: 'Node.js' },
};
/** Carpeta por defecto (igual que `Account::session_dir`). */
From af9b2713c0be7e0c82fa8960e8b8b281612fd7c3 Mon Sep 17 00:00:00 2001
From: danielgap
Date: Thu, 29 Oct 2026 23:29:18 +0100
Subject: [PATCH 2/3] chore(i18n): regenerar el inventario tras C1a (#66)
---
docs/i18n-inventory.md | 164 +++++++++++++++++++++++++++++------------
1 file changed, 116 insertions(+), 48 deletions(-)
diff --git a/docs/i18n-inventory.md b/docs/i18n-inventory.md
index 1890003..6ba83f7 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/` = `e59862776cb98564eb2fc5680c5ecb6991629266` and `src-tauri/src/` = `cda8f14546cb8d691b994ef70f9e1696811884bd`.
+Generated by `node scripts/i18n-inventory.mjs` from the source trees `src/` = `885c031291a237b5e8f97199076732e54483311d` and `src-tauri/src/` = `cda8f14546cb8d691b994ef70f9e1696811884bd`.
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/`) | 852 | 995 |
+| React (`src/`) | 906 | 1042 |
| Rust (`src-tauri/src/`) | 405 | 484 |
-| Total | 1222 | 1479 |
+| Total | 1276 | 1526 |
## src-tauri/src/channel.rs
@@ -570,15 +570,11 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a
- %USERPROFILE%\\.gemini\\oauth_creds.json
- %USERPROFILE%\\.grok\\auth.json
- (el host por región se cambia en Ajustes avanzados).
-- (elige «Login with Google»)
-- (si no te lo pide, escribe
- (solo lectura de la organización).
-- . 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.
- . Para WSL:
-- . Puedes usar una que ya tengas. Quorb lee
- /login
- /v1/usage
- 9Router
@@ -591,30 +587,21 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a
- API key de pago por uso (opcional)
- Admin API key (sk-ant-admin…)
- Admin key
-- Admin key de esta organización
-- Al guardar se borran también sus claves del almacén seguro del sistema.
- Anthropic API
-- Arriba, para distinguirla de la principal en la tira (por ejemplo, «Trabajo»).
- Avanzado · URL del API
- Avanzado · URL, facturación y endpoint de consumo
- Avanzado · base de datos de Cursor
-- Avanzado · carpeta de sesión
- Avanzado · dirección del servidor
- Avanzado · fichero de sesión y comando de login
- Base URL
- Base de datos de Cursor
- Business · 300
-- Cada cuenta guarda su sesión en su propia carpeta, así no toca la principal: el login se abre con
-- Cada organización tiene su propia
-- Carpeta de sesión
- ChatGPT (Plus / Pro · Codex)
- ChatGPT y Codex
- Claude
- Claude (Pro / Max)
- Comando de login
-- Comprobación
- 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 la cuenta o pega una API key
- Conecta tu cuenta o pega una API key
- Conecta tu dashboard local
- Conectado
@@ -626,9 +613,7 @@ 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 con esta cuenta en
- Créalo en
-- Cuenta adicional
- 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
@@ -636,7 +621,6 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a
- Cuotas diarias por modelo. Quorb lee la sesión que guarda
- Cursor
- Cómo conectar 9Router
-- Cómo conectar esta cuenta
- Cómo conectar la cuenta
- Del panel M/Token en
- Desconectar
@@ -644,18 +628,14 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a
- Docs
- Día de inicio de facturación
- Dónde escucha 9Router (por defecto http://localhost:20128).
-- El fichero existe, pero no confirma que la sesión siga siendo válida: míralo en la comprobación de abajo.
-- Eliminar esta cuenta
- Endpoint de consumo · URL
- English
- Enterprise · 1000
-- Entra con la cuenta que quieres añadir, no con la principal: si el navegador ya tiene abierta la principal, usa una ventana privada.
- Enviar enlace
- Español
- Etiqueta
- Fichero auth.json
- Fichero de credenciales
-- Fichero de sesión encontrado
- Free · 50
- GLM Premium
- Gasto del mes y de hoy, límite de la key (si tiene) y saldo de la cuenta.
@@ -669,22 +649,12 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a
- GitHub › Fine-grained tokens
- Grok
- Grok (SuperGrok)
-- Guarda la cuenta para comprobar que funciona.
-- Guardar e iniciar sesión
-- Guardar y comprobar
- Guardar y probar
-- Hace falta para iniciar sesión y para renovarla cuando caduque, aunque ahora funcione.
- 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
-- Inicia sesión con la otra cuenta
- Inicia sesión en la app de Cursor
-- Iniciar sesión
-- Instala
- JSON Pointer · límite
- JSON Pointer · usado
-- 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.
- 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.
@@ -697,7 +667,6 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a
- Mostrar gasto del mes
- NaN Builders
- No muestra un sub-orbe hasta que su gasto en el periodo supere esta cifra. 0 = cualquiera que gaste.
-- Nombre
- Opcional: pinta la barra de gasto del periodo frente al tope que pongas.
- OpenAI API
- OpenCode Go
@@ -708,28 +677,22 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a
- Pago por uso · API oficial
- Pago por uso · Admin key
- Pasarela local · uso agregado
-- Pega el token de esta cuenta
- 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
-- Ponle nombre
- Presupuesto (USD)
- Presupuesto mensual (opcional)
- Pro · 300
- Pro+ · 1500
- Probar la API de NaN con la key
-- Puede ser de otra cuenta o de la misma con otro límite.
- 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 abre una terminal con
-- Se hace sola en cuanto guardes e inicies sesión.
- Se llama con tu key como Bearer y debe devolver JSON.
-- Se ve en la tira junto al proveedor.
- 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
@@ -745,8 +708,6 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a
- Te hemos enviado un enlace. Cópialo (sin abrirlo) y pégalo aquí.
- Todo
- Token de GitHub (fine-grained)
-- Token de GitHub de esta cuenta (fine-grained)
-- Token de la cuenta
- Token de solo lectura
- Token manual (opcional)
- USD al mes
@@ -757,14 +718,10 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a
- Vacío =
- Ver respuestas de /v1/models y /v1/usage
- Volver
-- Volver a abrir el login
- Wayland
- \\\\wsl.localhost\\Ubuntu\\home\\TU_USUARIO\\.claude\\.credentials.json
-- apuntando a ella. Si la comprobación pide renovar la sesión, «Volver a abrir el login» abre de nuevo el CLI.
- cloud.nan.builders
- con el permiso de cuenta «Plan»: solo lectura.
-- con el permiso «Plan» (solo lectura).
-- dentro de ella.
- dos credenciales distintas
- egdev.es
- egdev6
@@ -778,7 +735,6 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a
- openrouter.ai › Keys
- p. ej. /usage/quota_tokens
- p. ej. /usage/used_tokens
-- su CLI
- tu@correo.com
- y las cuotas publicadas.
- ya registra en tu equipo: peticiones, tokens y coste por proveedor del periodo. Además del orbe agregado, cada proveedor que haya visto (Claude, Kiro, GLM…) aparece como su propia cuenta.
@@ -847,8 +803,12 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a
## src/i18n/en.ts
+- (choose “Login with Google”)
+- (if it does not ask, type
- (needs {needs})
- + Add account
+- . The browser does the official login and the CLI stores the session, which Quorb only reads.
+- . You can use one you already have. Quorb reads
- 1 reset available
- 1 · When the limit is exceeded
- 2 · When it resets
@@ -860,14 +820,23 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a
- A new version is available: {version}.
- A single ring per subscription: the closest limit.
- A step is missing
+- A terminal opens with
+- API key
+- API key (alternative)
- APIs and credits
- Above the rest of the windows.
+- Above, to tell it apart from the main one on the strip (for example, “Work”).
- Accent color
+- Account token
- Accounts
- Action
- Actions
- Active
- Active but not set up
+- Additional account
+- Admin key
+- Admin key for this organization
+- Advanced · session folder
- Alert when reaching
- Alert when the exceeded limit resets
- Alerts
@@ -884,6 +853,7 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a
- Budget
- Change
- ChatGPT and Codex
+- Check
- Check every
- Check for updates automatically
- Check now
@@ -901,6 +871,7 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a
- Configuration
- Configure
- Connect
+- Connect the account or paste an API key
- Consumption pace
- Copied
- Copied ✓
@@ -912,6 +883,8 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a
- Could not read “Start at login”:
- Could not save the language
- Could not update Quorb
+- Create it at
+- Create it with this account at
- Current version
- Custom effect
- Custom effect (video or animated image)
@@ -924,8 +897,11 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a
- Disabled
- Done
- Drag: snaps to the nearest edge
+- Each account keeps its session in its own folder, so it does not touch the main one: the login opens with
+- Each organization has its own
- Edge
- Effect
+- Empty =
- Enable alerts
- Enable shortcuts
- Error
@@ -934,18 +910,26 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a
- File
- Full screen
- GLM Coding Plan · 5 h, weekly quotas and web tools
+- GitHub token for this account (fine-grained)
+- GitHub user and plan
+- GitHub username
- Global shortcuts
- Go / Go Plus · 5 h, weekly and billing-cycle quotas
- Hide on its own
- History
- History range
+- How to connect this account
- In addition to the on-screen effect.
- Install and restart
+- Install {name}
- Installed version
- Installed version, automatic updates and the project.
- Installing
+- It can be from another account or from the same one with a different limit.
- It is downloaded, the signature is verified and Quorb restarts.
+- It is needed to sign in and to renew the session when it expires, even if it works now.
- It is the one on the outer ring and the first bar; the other two tones come out on their own. Alerts and overflow keep their alert colors.
+- It runs on its own as soon as you save and sign in.
- Key expires
- Keyboard shortcuts
- Keyboard shortcuts that work from any app.
@@ -959,6 +943,9 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a
- Manual updates on macOS. Updates are not checked for or installed from the app.
- Medium
- Month spend with an admin key
+- Monthly budget (optional)
+- Name
+- Name it
- Needs connecting
- No file
- No known time window to calculate the pace.
@@ -972,21 +959,26 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a
- Official usage ↗
- Once per window (5 h session, week, month…).
- Open its panel and press "Renew session". Quorb refreshes itself as soon as it renews.
+- Open the login again
- Open the most loaded
- Open the official CLI to renew the session; the widget refreshes itself when it finishes
- Opus
+- Other amount
- Pace and history
- Pace {pace}× · runs out {when}
- Pace {pace}× · you will not make it to the reset
- Pace {pace}× · you would end at {pct} %
- Paste the key here
+- Paste this account token
- Period through
- Personalization not applied
- Pick any color (RGB)
+- Plan
- Plan quota and tokens per day
- 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
- Press a shortcut or “Change” and type the combination. They apply when you save.
- Press the combination… (Esc cancels)
@@ -1000,8 +992,10 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a
- Quorb is open source (MIT). If it saves you a scare with the limits, a star on GitHub helps more people find it.
- Quorb signs in with its official login. Install it from {where}{needs}; Quorb detects it on its own when it finishes.
- Quorb {version} available
+- Read-only token
- Refresh now
- Remove
+- Remove this account
- Removed
- Renew session
- Request a provider
@@ -1018,11 +1012,16 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a
- Rings
- Save
- Save and check
+- Save and sign in
- Save changes
+- Save the account to check that it works.
- Save the changes to check this account.
- Saved to the system secure store
- Saved ✓
+- Saving also deletes its keys from the system secure store.
- Saving…
+- Session file found
+- Session folder
- Set up
- Settings
- Settings not saved: {error}
@@ -1031,9 +1030,13 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a
- Shortcut
- Shortcuts
- Show Quorb
+- Show in the widget
- Show or hide the strip
- Show resets on the circles
+- Shown on the strip next to the provider.
- Sign in
+- 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
- Size
- Small
@@ -1051,6 +1054,9 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a
- System notification
- Test
- 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.
+- The file exists, but it does not confirm the session is still valid: check the verification below.
- The month has just started
- The resets balance always stays available in the detail panel.
- The window has just started
@@ -1064,6 +1070,7 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a
- Top
- Transparency
- Tray icon
+- USD per month
- Up to date
- Update now
- Update to Quorb {version}
@@ -1091,9 +1098,12 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a
- in {h} h
- in {h} h {m} min
- in {mins} min
+- inside it.
+- its CLI
- max. {value}
- no known limits:
- no shortcut
+- pointing to it. If the check asks you to renew the session, “{reopen}” opens the CLI again.
- resets {when}
- scale 0–{max} %
- the native geometry could not be read
@@ -1102,10 +1112,12 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a
- the window manager did not grant the size
- the window manager did not place the strip where it was asked
- with limits:
+- with the “Plan” permission (read-only).
- {count} resets available
- {day}: {value} tokens
- {label} runs out{when}, before the reset.
- {label}: {value}. Press to change it
+- {name} API key
- {name} details
- {name} is missing on this computer.
- {name}: session expired
@@ -1117,8 +1129,12 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a
## src/i18n/es.ts
+- (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.
+- . Puedes usar una que ya tengas. Quorb lee
- 1 reset disponible
- 1 · Al superar el límite
- 2 · Al reiniciarse
@@ -1127,6 +1143,9 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a
- 7 días
- ; si la firma no coincide, no se instalan.
- A este ritmo
+- API key
+- API key (alternativa)
+- API key de {name}
- APIs y créditos
- Abajo
- Abre el CLI oficial, que renueva la sesión; el widget se actualiza solo al terminar
@@ -1146,9 +1165,12 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a
- Actualizar a Quorb {version}
- Actualizar ahora
- Además del efecto en pantalla.
+- Admin key
+- Admin key de esta organización
- Ajustes
- Al arrancar y cada 6 h. Si hay una nueva, aparece un icono de descarga en la tira.
- Al día
+- Al guardar se borran también sus claves del almacén seguro del sistema.
- Al ritmo actual: {amount} a fin de mes
- Alertas
- Anillos
@@ -1159,11 +1181,13 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a
- Arranque al iniciar sesión
- Arrastrar: se pega al borde más cercano
- Arriba
+- Arriba, para distinguirla de la principal en la tira (por ejemplo, «Trabajo»).
- Atajo
- Atajos
- Atajos de teclado
- Atajos globales
- Atención
+- Avanzado · carpeta de sesión
- Avisar al llegar a
- Avisar cuando el límite superado se reinicia
- Aún no hay lecturas guardadas.
@@ -1172,9 +1196,12 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a
- Buscando…
- Buscar actualizaciones solo
- Buscar ahora
+- Cada cuenta guarda su sesión en su propia carpeta, así no toca la principal: el login se abre con
+- Cada organización tiene su propia
- Caducidad de la clave comunicada por el proveedor
- Cambiar
- Cargando…
+- Carpeta de sesión
- Cerrar
- Cerrar la configuración
- ChatGPT y Codex
@@ -1183,9 +1210,11 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a
- Color de acento
- Colores resultantes
- Combinaciones de teclado que funcionan desde cualquier app.
+- Comprobación
- Comprobando la conexión…
- Comprobar ahora
- Con sonido
+- Conecta la cuenta o pega una API key
- Conectar
- Confeti
- Configuración
@@ -1201,12 +1230,16 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a
- Copiado ✓
- Copiar
- Copiar resumen
+- Créala en
+- Créalo con esta cuenta en
+- Cuenta adicional
- Cuentas
- Cuota del plan M/Token y saldo de pago por uso
- Cuota del plan y tokens por día
- Cuota diaria por modelo
- Cuándo avisar
- Círculos pequeños
+- Cómo conectar esta 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.
@@ -1216,11 +1249,14 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a
- Efecto
- Efecto propio
- Efecto propio (vídeo o imagen animada)
+- El fichero existe, pero no confirma que la sesión siga siendo válida: míralo en la comprobación de abajo.
- El saldo de resets siempre sigue disponible en el panel de detalle.
- Elegir cualquier color (RGB)
- Elegir…
- Eliminada
+- Eliminar esta cuenta
- Eliminar key
+- Entra con la cuenta que quieres añadir, no con la principal: si el navegador ya tiene abierta la principal, usa una ventana privada.
- Error
- Es el del anillo exterior y la primera barra; los otros dos tonos salen solos. Los avisos y el exceso conservan sus colores de alerta.
- Expandir el menú
@@ -1228,6 +1264,7 @@ 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 de sesión encontrado
- Fin del periodo comunicado por el proveedor; no confirma cancelación
- Funciona
- Funcionan aunque Quorb no tenga el foco.
@@ -1236,29 +1273,38 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a
- Gasto del mes con clave de admin
- Glow de peligro
- Go / Go Plus · cuotas de 5 h, semanal y ciclo de facturación
+- Guarda la cuenta para comprobar que funciona.
- Guarda los cambios para comprobar esta cuenta.
- Guardada en el almacén seguro del sistema
- Guardado ✓
- Guardando…
- Guardar
- Guardar cambios
+- Guardar e iniciar sesión
- Guardar y comprobar
+- Hace falta para iniciar sesión y para renovarla cuando caduque, aunque ahora funcione.
- Hay una versión nueva: {version}.
- Historial
- Historial de consumo
- Icono de bandeja
- Idioma
+- Inicia sesión
+- Inicia sesión con la otra cuenta
- Iniciar sesión
- Inicio al iniciar sesión guardado
- Inicio al iniciar sesión no guardado: {error}
+- Instala {name}
- Instalando
- Instalar y reiniciar
- 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.
- Listo
- Llegan firmadas desde
- Los subproveedores de 9Router se descubren solos desde su historial de uso; no se añaden a mano.
- Mediano
- Mostrar Quorb
+- Mostrar en el widget
- Mostrar resets en los círculos
- Mostrar u ocultar la tira
- Ninguno
@@ -1270,30 +1316,38 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a
- No se pudo determinar el modo de actualización. Reinicia Quorb para reintentar.
- No se pudo guardar el idioma
- No se pudo leer «Arrancar al iniciar sesión»:
+- Nombre
- Notificaciones
- Notificación del sistema
- Ocultar sola
- Opacidad de la tira y el panel
- Opus
+- Otra cantidad
- Pantalla completa
- Para cambiar el orden de los anillos, arrástralos en la tira.
- Pasarela local · uso y gasto agregados
- Pedir un proveedor
- Pega aquí la key
+- Pega el token de esta cuenta
- Pequeño
- Periodo hasta
- Personalización no aplicada
- Peticiones
- Peticiones premium del mes
+- Peticiones premium incluidas al mes
+- Plan
- Plus / Pro · sesión y semanal de Codex
+- Ponle nombre
- Por encima del resto de ventanas.
- Posición en el borde
- Presupuesto
+- Presupuesto mensual (opcional)
- Principal
- Pro / Max · sesión de 5 h, semanal y por modelo
- Probar
- Proveedores
- Proyecto
+- Puede ser de otra cuenta o de la misma con otro límite.
- Pulsa el icono de descarga de la tira para actualizar.
- Pulsa la combinación… (Esc cancela)
- Pulsa un atajo o «Cambiar» y teclea la combinación. Se aplican al guardar.
@@ -1316,9 +1370,12 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a
- Ritmo {pace}× · no llegas al reinicio
- Ritmo {pace}× · se agota {when}
- Saldo y gasto por clave
+- Se abre una terminal con
- Se aplica al momento en todas las ventanas.
- Se descarga, se comprueba la firma y Quorb se reinicia.
+- Se hace sola en cuanto guardes e inicies sesión.
- Se reinicia {when}
+- Se ve en la tira junto al proveedor.
- Secciones de ajustes
- Semana
- Sesión de escritorio: {session}
@@ -1339,24 +1396,35 @@ 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 de esta cuenta (fine-grained)
+- Token de la cuenta
+- Token de solo lectura
- Tokens
- Tokens por día
- Tokens por día · 30 días
- Transparencia
+- USD al mes
- Un solo anillo por suscripción: el límite más cercano.
- 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 y on-demand
- Uso oficial ↗
+- Usuario de GitHub
+- Usuario y plan de GitHub
+- Vacío =
- Versión actual
- Versión instalada
- Versión instalada, actualizaciones automáticas y el proyecto.
- Vista previa a {size} px
+- Volver a abrir el login
- Vídeo o imagen animada
+- 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 «Plan» (solo lectura).
- con límites:
+- dentro de ella.
- el gestor no colocó la tira donde se pidió
- el gestor no concedió el tamaño
- en {d} d
@@ -1374,6 +1442,7 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a
- saldo comunicado por z.ai; cada reset vence y se usa manualmente
- sin atajo
- sin límites conocidos:
+- su CLI
- una terminal
- {count} resets disponibles
- {day}: {value} tokens
@@ -1396,7 +1465,6 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a
- irm https://claude.ai/install.ps1 \| iex
- npm install -g @google/gemini-cli
- npm install -g @openai/codex
-- una terminal
## src/lib/alerts.ts
From 763d1ce68993aa60be530d2358d135c64b5d1584 Mon Sep 17 00:00:00 2001
From: egdev6
Date: Thu, 29 Oct 2026 23:50:25 +0100
Subject: [PATCH 3/3] docs: regenerar el inventario i18n tras integrar main
(#66)
---
docs/i18n-inventory.md | 14 ++++++++++----
1 file changed, 10 insertions(+), 4 deletions(-)
diff --git a/docs/i18n-inventory.md b/docs/i18n-inventory.md
index 6ba83f7..1a03257 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/` = `885c031291a237b5e8f97199076732e54483311d` and `src-tauri/src/` = `cda8f14546cb8d691b994ef70f9e1696811884bd`.
+Generated by `node scripts/i18n-inventory.mjs` from the source trees `src/` = `a03110502887df4b7126120c13f164bc9a812e9a` 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/`) | 906 | 1042 |
-| Rust (`src-tauri/src/`) | 405 | 484 |
-| Total | 1276 | 1526 |
+| React (`src/`) | 908 | 1044 |
+| Rust (`src-tauri/src/`) | 409 | 488 |
+| Total | 1281 | 1532 |
## src-tauri/src/channel.rs
@@ -439,6 +439,7 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a
## src-tauri/src/widget_placement.rs
- Invalid widget placement scale: {}
+- Wayland
- Widget bounds UI callback was dropped
- the native window could not be read
- the native window origin could not be read
@@ -527,8 +528,10 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a
- Click-through del widget no implementado: el ratón no atraviesa la tira; solo el overlay de efectos lo usa.
- El compositor aplica transparencia.
- El icono de bandeja se muestra en los gestores que la evalúan.
+- El posicionamiento absoluto de ventanas no está disponible bajo xdg-shell en Wayland: el arrastre de la tira se delega al compositor.
- El registro puede denegarse en Wayland: el error se muestra en Ajustes › Atajos.
- El soporte de siempre-encima depende del compositor; si la tira se oculta, usa «Auto-hide».
+- En X11 (y bajo XWayland con GDK_BACKEND=x11) el anclaje al borde y el arrastre con imán funcionan; la tira alcanza el borde del área de trabajo (#205).
- En X11 la tira se mantiene sobre las demás ventanas.
- GNOME Wayland (Mutter) puede ignorar hints de always-on-top en algunos escenarios. La tira recurre a posición fija + skip_taskbar.
- Host StatusNotifier detectado en la sesión: hay dónde mostrar el icono. La extensión «AppIndicator» de Ubuntu está habilitada. Solo se acredita la disponibilidad del host, no la visibilidad del icono.
@@ -544,6 +547,7 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a
- Los atajos globales dependen del registro por portal; si falla, el error se ve en Ajustes › Atajos.
- Los atajos globales funcionan desde cualquier app.
- Mutter admite hints de siempre-encima de forma parcial; si la tira se oculta tras ventanas, usa «Auto-hide».
+- Mutter no expone posicionamiento absoluto de ventanas (gtk_window_move no tiene efecto): el arrastre usa el gesto del compositor y, al soltar, no hay imán ni se persiste la posición.
- Mutter puede ignorar la región de input: el ratón puede quedar capturado en el hueco; prueba a mover la tira.
- No se detectó ningún host StatusNotifier en la sesión: no hay dónde mostrar el icono de bandeja. La extensión «AppIndicator» está habilitada, pero la barra activa no provee el host (¿el dock fue reemplazado?).
- No se detectó ningún host StatusNotifier en la sesión: no hay dónde mostrar el icono. Instalá la extensión «AppIndicator» de Ubuntu para proveerlo.
@@ -1085,6 +1089,7 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a
- 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
- Works
- You can also drag it from its handle: it sticks to the closest edge.
@@ -1339,6 +1344,7 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a
- Plus / Pro · sesión y semanal de Codex
- Ponle nombre
- Por encima del resto de ventanas.
+- Posicionamiento de la ventana
- Posición en el borde
- Presupuesto
- Presupuesto mensual (opcional)