diff --git a/docs/i18n-inventory.md b/docs/i18n-inventory.md index b239a2c..3b2685f 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/` = `0cda9fef36e559dcffcd3bf53611c07f89c49a2b` and `src-tauri/src/` = `fa2fd75f1124453a881a835169cde8f4ffb08839`. +Generated by `node scripts/i18n-inventory.mjs` from the source trees `src/` = `a84d5689731244c444764acff917bddac731d289` and `src-tauri/src/` = `fa2fd75f1124453a881a835169cde8f4ffb08839`. 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/`) | 958 | 1069 | +| React (`src/`) | 1049 | 1147 | | Rust (`src-tauri/src/`) | 508 | 625 | -| Total | 1428 | 1694 | +| Total | 1518 | 1772 | ## src-tauri/src/channel.rs @@ -724,58 +724,27 @@ 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 -- (solo lectura de la organización). - /login - /v1/usage - 9Router -- 9Router conectado · volver a conectar -- 9Router desconectado -- API key -- API key (alternativa) - API key PAYG - Admin API key (sk-ant-admin…) - Admin key - Anthropic API - Antigravity -- Avanzado · URL, facturación y endpoint de consumo -- Avanzado · dirección del servidor - Base URL - Business · 300 - ChatGPT (Plus / Pro · Codex) - ChatGPT y Codex - Claude - Claude (Pro / Max) -- 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 -- Conectado -- Conectado como ${…} -- Conectar -- Conectar con el dashboard -- Conectar tu cuenta (recomendado) -- Consultando… -- Consumo -- Contraseña del dashboard -- Créala en -- Cuenta de NaN Cloud conectada - Cursor -- Cómo conectar 9Router -- Cómo conectar la cuenta -- Desconectar -- Desconectar 9Router - Docs -- Día de inicio de facturación -- Dónde escucha 9Router (por defecto http://localhost:20128). -- Endpoint de consumo · URL - English - Enterprise · 1000 -- Enviar enlace - Español -- Etiqueta - Free · 50 - GLM Premium -- Gasto del mes y de hoy, límite de la key (si tiene) y saldo de la cuenta. -- Gasto mínimo por sub-proveedor (USD) - Gemini - Gemini (Google AI Pro / Code Assist) - Gemini CLI @@ -785,51 +754,17 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a - GitHub › Fine-grained tokens - Grok - Grok (SuperGrok) -- 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. -- 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 -- Los ocultaste desde su panel en la tira. Pulsa para volver a mostrarlos. - MiniMax -- Mostrar cada sub-proveedor como su propia cuenta -- Mostrar en el widget -- 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. -- Opcional: pinta la barra de gasto del periodo frente al tope que pongas. - OpenAI API - OpenCode Go - OpenCode › Console - OpenRouter - Organization › Admin keys -- Pago por uso · API oficial -- Pago por uso · Admin key -- Pasarela local · uso agregado -- Periodo de uso -- Periodo y desglose -- Plan -- Presupuesto (USD) -- Presupuesto mensual (opcional) - Pro · 300 - Pro+ · 1500 -- Probar la API de NaN con la key -- Quitar - Quorb -- Quorb lee el uso y el gasto que -- Rango que muestra el orbe y su historial. -- Se llama con tu key como Bearer y debe devolver JSON. -- Solo para GLM 5.3 (cuenta por periodo de Stripe) cuando usas la API key. -- Suborbes ocultos de 9Router - Subscription Key -- Suscripción · cuenta o API key -- Te hemos enviado un enlace. Cópialo (sin abrirlo) y pégalo aquí. -- Todo -- USD al mes -- Ver respuestas de /v1/models y /v1/usage -- Volver - Wayland - \\.claude\\.credentials.json - \\\\wsl.localhost\\Ubuntu\\home\\ @@ -842,19 +777,8 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a - nan_community - nan_member - openrouter.ai › Keys -- p. ej. /usage/quota_tokens -- p. ej. /usage/used_tokens -- 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. - z.ai - z.ai › API Keys -- · mostrar -- Últimas 24 h -- Últimos 30 días -- Últimos 60 días -- Últimos 7 días -- •••••••• (configurada) - › API Keys ## src/Widget.tsx @@ -865,40 +789,6 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a ## src/components/CodexInstall.tsx -- , dentro de la carpeta de Quorb. No se toca el npm global ni las variables de entorno. -- Antes de instalar, Quorb solo comprueba que Node.js y npm existan (sin ejecutarlos); la versión mínima se valida al confirmar la instalación. No se instala Node ni se piden permisos de administrador. -- Carpeta de destino: -- Codex CLI instalado. -- Codex está instalado; revisa tu comando de login. -- Comprobando Codex CLI… -- Confirma en la ventana de Windows si aparece. Puedes cancelar desde ahí. -- Copiar orden -- El instalador está trabajando en otra ventana. -- Espera a que termine; Quorb lo detecta solo. -- Este sistema no permite la instalación asistida. -- Está pensada para Windows; puedes instalarlo a mano y Quorb lo detectará. -- Falta Codex CLI en este equipo. -- Instalación cancelada. -- Instalando Codex CLI… -- Instalando… -- Instalar Codex -- La instalación no crea la sesión: usa «Iniciar sesión» cuando quieras. -- No se cambió nada. Puedes intentarlo de nuevo. -- No se detecta Node.js y npm. -- No se pudo consultar el instalador. -- No se pudo instalar Codex CLI. -- Paquete fijado: -- Puedes reintentarlo. -- Quorb puede instalarlo desde aquí: Windows pedirá confirmación antes de instalar nada. -- Quorb solo comprueba que existan; la versión (Node.js 22 o superior) la valida el instalador al confirmar. Instálalos y vuelve a comprobar. -- Quorb solo consulta el estado del equipo. -- Quorb ya detecta el CLI en este equipo. -- Qué instala exactamente -- Reintentar -- Se ignoran los scripts de ciclo de vida del paquete y se valida la versión real antes de darlo por bueno. -- Volver a comprobar -- Vuelve a comprobar el estado. -- desde el registro oficial de npm. No se instala «la última». - npm install -g ${…} ## src/components/ProviderLogo.tsx @@ -915,8 +805,10 @@ 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}) +- (organization read-only). - (the per-region host is changed in Advanced settings). - + Add account +- , inside the Quorb folder. The global npm and the environment variables are not touched. - . In mainland China, at - . The browser does the official login and the CLI stores the session, which Quorb only reads. - . Unofficial endpoint. @@ -928,11 +820,14 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a - 24 h - 30 days - 7 days +- 9Router connected · reconnect +- 9Router disconnected - ; if the signature does not match, they are not installed. - A WebM (VP9) video with an alpha channel or an animated GIF, WebP or APNG image with transparency. It shows up centered above everything, without blocking the mouse; the video lasts as long as the video itself (max. 20 s) and images 4.5 s. - A new version is available: {version}. - A single ring per subscription: the closest limit. - A step is missing +- A sub-orb is not shown until its period spend exceeds this figure. 0 = show any with spend. - A terminal opens with - API key - API key (alternative) @@ -951,11 +846,14 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a - Admin key for this organization - Advanced · API URL - Advanced · Cursor database +- Advanced · URL, billing and usage endpoint +- Advanced · server address - Advanced · session file and login command - Advanced · session folder - Alert when reaching - Alert when the exceeded limit resets - Alerts +- All - Always on top - Appearance - Applies right away in every window. @@ -964,15 +862,22 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a - At the current pace: {amount} by the end of the month - At this pace - Attention +- Back - Balance and spend per key +- Before installing, Quorb only checks that Node.js and npm exist (without running them); the minimum version is validated on confirming the installation. Node is not installed and no administrator permissions are requested. +- Billing start day - Bottom - Budget +- Budget (USD) - Change - ChatGPT and Codex - Check +- Check again - Check every - Check for updates automatically - Check now +- Check the state again. +- Checking Codex CLI… - Checking the connection… - Checking the update mode… - Checking with what is saved… @@ -981,19 +886,32 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a - Click-through gaps - Close - Close the configuration +- Codex CLI could not be installed. +- Codex CLI installed. +- Codex CLI is missing on this machine. +- Codex is installed; check your login command. - 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 - Configuration - Configure +- Confirm in the Windows window if it appears. You can cancel from there. - Connect - Connect the account or paste an API key +- Connect to the dashboard +- Connect your account (recommended) +- Connect your account or paste an API key +- Connect your local dashboard +- Connected +- Connected as {who} - Consumption pace - Copied - Copied ✓ - Copy +- Copy command - Copy summary +- Cost APIs do not support OAuth: you need an - Could not check right now: {error}. Try again. - Could not copy - Could not determine the update mode. Restart Quorb to try again. @@ -1010,11 +928,14 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a - Daily per-model quotas. Quorb reads the session stored by - Daily quota per model - Danger glow +- Dashboard password - Data from the last good cycle - Data received - Delete key - Desktop session: {session} - Disabled +- Disconnect +- Disconnect 9Router - 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 @@ -1038,30 +959,47 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a - GitHub username - Global shortcuts - Go / Go Plus · 5 h, weekly and billing-cycle quotas +- Hidden 9Router sub-orbs - Hide on its own - History - History range +- How to connect 9Router - How to connect the account - How to connect this account - In addition to the on-screen effect. +- Install Codex - Install and restart - Install {name} +- Installation cancelled. - Installed version - Installed version, automatic updates and the project. - Installing +- Installing Codex CLI… +- Installing… - It can be from another account or from the same one with a different limit. +- It is called with your key as Bearer and must return JSON. +- It is designed for Windows; you can install it manually and Quorb will detect it. - 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. +- JSON Pointer · limit +- JSON Pointer · used +- Just like «nan auth login --email»: NaN sends you a single-use link. Do not open it in the browser (it would be spent): copy it and paste it here. The session is saved in the system credential store and gives the same data as your dashboard. - 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. +- Label - Language +- Last 24 h +- Last 30 days +- Last 60 days +- Last 7 days - 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 - Local gateway · aggregated usage and spend - Login command - M/Token plan quota and pay-as-you-go balance @@ -1069,8 +1007,10 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a - Manual token (optional) - Manual updates on macOS. Updates are not checked for or installed from the app. - Medium +- Minimum spend per sub-provider (USD) - Month spend with an admin key - Monthly budget (optional) +- NaN Cloud account connected - Name - Name it - Needs connecting @@ -1078,31 +1018,40 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a - No known time window to calculate the pace. - No readings saved yet. - No shortcut +- Node.js and npm are not detected. - None - Not enough readings yet. New ones are saved every few minutes. - Not set up - Not working +- Nothing was changed. You can try again. - Notifications - Official usage ↗ - Once per window (5 h session, week, month…). +- Only for GLM 5.3 (billed by Stripe period) when using the API key. - Open Antigravity with your Google account - 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 +- Optional: shows the period spend bar against your cap. - 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} % +- Package lifecycle scripts are ignored and the real version is validated before accepting it. - Paste the key here - Paste this account token - Pay-as-you-go API key (optional) +- Pay-as-you-go · Admin key +- Pay-as-you-go · Official API - Per-model agent quota · open IDE +- Period and breakdown - Period through - Personalization not applied - Pick any color (RGB) +- Pinned package: - Plan - Plan Subscription Key - Plan quota and tokens per day @@ -1123,13 +1072,19 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a - Provider-reported key expiry - Provider-reported period end; does not confirm cancellation - Providers +- Quorb already detects the CLI on this machine. +- Quorb can install it from here: Windows will ask for confirmation before installing anything. - Quorb is open source (MIT). If it saves you a scare with the limits, a star on GitHub helps more people find it. +- Quorb only checks that they exist; the version (Node.js 22 or higher) is validated by the installer on confirmation. Install them and check again. +- Quorb only checks the state of this machine. - Quorb queries the local server the desktop IDE starts on this computer. It is read only while that IDE is open; no token is stored. The macOS agy CLI does not expose the CSRF token, so that session cannot be read. - 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 reads the usage and spend that - 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. - Quota of each Antigravity agent model (5-hour and weekly windows). It is a separate pool from Gemini CLI. Unofficial local endpoint. Desktop IDE session. The macOS agy CLI does not expose the CSRF token this endpoint requires. +- Range shown by the orb and its history. - Read-only token - Refresh now - Remove @@ -1151,6 +1106,7 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a - Save - Save and check - Save and sign in +- Save and test - Save changes - Save the account to check that it works. - Save the changes to check this account. @@ -1158,6 +1114,8 @@ 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… +- See /v1/models and /v1/usage responses +- Send link - 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 @@ -1169,7 +1127,9 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a - Shortcut - Shortcuts - Show Quorb +- Show each sub-provider as its own account - Show in the widget +- Show month spend - Show or hide the strip - Show resets on the circles - Shown on the strip next to the provider. @@ -1193,25 +1153,35 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a - Subscription · Go / Go Plus - Subscription · OAuth - Subscription · Official API +- Subscription · account or API key - Subscription · app session - Subscription · desktop IDE - Subscriptions - Subscriptions & APIs - SuperGrok · weekly CLI credit usage - System notification +- Target folder: - Test +- Test the NaN API with your key - The 5 h and weekly limits of your ChatGPT plan in Codex. Quorb reads the session that the Codex CLI stores. +- The 9Router dashboard password («123456» by default until you change it). It is saved in the system secure store. - 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 installation does not create the session: use «Sign in» whenever you want. +- The installer could not be queried. +- The installer is working in another window. - The month has just started - The resets balance always stays available in the detail panel. - The window has just started - They arrive signed from - They work even when Quorb is not focused. +- This month's and today's spend, key limit (if any) and account balance. +- This system does not support assisted installation. - Time elapsed in the window - To change the order of the rings, drag them on the strip. +- Today - Tokens - Tokens per day - Tokens per day · 30 days @@ -1225,26 +1195,39 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a - Updated {when} - Updates - Updating to {version}{detail} +- Usage +- Usage endpoint · URL - Usage history +- Usage period - Use {color} - Video or animated image - Videos only. +- Wait for it to finish; Quorb detects it on its own. +- We have sent you a link. Copy it (without opening it) and paste it here. - Week - Weekly credit usage of your subscription in the Grok CLI. Quorb reads the session stored by +- What exactly it installs - What gets checked and how each provider connects. - What happens when a limit is exceeded and when it resets. - When to alert +- Where 9Router listens (http://localhost:20128 by default). - Window positioning - With sound +- With your account connected you see the same as your dashboard: tokens used against the quota of each model, when it resets and the daily series. Without an account, the API key uses - 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 can retry. - You have unsaved changes: the check uses what is saved. +- You hid them from their panel in the strip. Click to show them again. - a terminal +- already records on your machine: requests, tokens and cost per provider of the period. Besides the aggregated orb, every provider it has seen (Claude, Kiro, GLM…) appears as its own account. +- and the published quotas. - 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 +- from the official npm registry. The latest version is not installed. - in the console: the first gives access to the plan and the second to the balance. - in {d} d - in {d} d {h} h @@ -1288,7 +1271,9 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a - (elige «Login with Google») - (necesita {needs}) - (si no te lo pide, escribe +- (solo lectura de la organización). - + Añadir cuenta +- , dentro de la carpeta de Quorb. No se toca el npm global ni las variables de entorno. - . 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. @@ -1300,6 +1285,8 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a - 24 h - 30 días - 7 días +- 9Router conectado · volver a conectar +- 9Router desconectado - ; si la firma no coincide, no se instalan. - A este ritmo - API key @@ -1335,6 +1322,7 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a - Al ritmo actual: {amount} a fin de mes - Alertas - Anillos +- Antes de instalar, Quorb solo comprueba que Node.js y npm existan (sin ejecutarlos); la versión mínima se valida al confirmar la instalación. No se instala Node ni se piden permisos de administrador. - Apariencia - Apps - Archivo @@ -1349,8 +1337,10 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a - Atajos globales - Atención - 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 - Avisar al llegar a - Avisar cuando el límite superado se reinicia @@ -1366,40 +1356,57 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a - Caducidad de la clave comunicada por el proveedor - Cambiar - Cargando… +- Carpeta de destino: - Carpeta de sesión - Cerrar - Cerrar la configuración - ChatGPT y Codex - Clave caduca - Clic a través de los huecos +- Codex CLI instalado. +- Codex está instalado; revisa tu comando de login. - Color de acento - Colores resultantes - Comando de login - Combinaciones de teclado que funcionan desde cualquier app. - Comprobación +- Comprobando Codex CLI… - Comprobando la conexión… - Comprobar ahora +- 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 - Con sonido - Conecta la cuenta o pega una API key +- Conecta tu cuenta o pega una API key +- Conecta tu dashboard local +- Conectado +- Conectado como {who} - Conectar +- Conectar con el dashboard +- Conectar tu cuenta (recomendado) - Confeti - Configuración - Configuración guardada - Configuración no guardada: {error} - Configurar +- Confirma en la ventana de Windows si aparece. Puedes cancelar desde ahí. - Consultando con lo guardado… - Consultando el modo de actualización… +- Consultando… - Consultar cada +- Consumo - Contraer - Contraer el menú +- Contraseña del dashboard - Copiado - Copiado ✓ - Copiar +- Copiar orden - Copiar resumen - Créala en - Créalo con esta cuenta en - Créalo en - Cuenta adicional +- Cuenta de NaN Cloud conectada - Cuentas - Cuota de cada modelo del agente de Antigravity (ventanas de 5 h y semanales). Es un grupo distinto del de Gemini CLI. Endpoint local no oficial. Sesión del IDE de escritorio. El agy CLI en macOS no expone el token CSRF que exige este endpoint. - Cuota de las últimas 5 h, cuota semanal y cuota del ciclo de facturación de la suscripción Go / Go Plus. @@ -1412,6 +1419,7 @@ 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 - Cuándo avisar - Círculos pequeños +- Cómo conectar 9Router - Cómo conectar esta cuenta - Cómo conectar la cuenta - Datos del último ciclo correcto @@ -1420,22 +1428,34 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a - Del panel M/Token en - Derecha - Desactivado +- Desconectar +- Desconectar 9Router - Detalle de {name} +- Día de inicio de facturación +- Dónde escucha 9Router (por defecto http://localhost:20128). - 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 instalador está trabajando en otra ventana. - El saldo de resets siempre sigue disponible en el panel de detalle. - Elegir cualquier color (RGB) - Elegir… - Eliminada - Eliminar esta cuenta - Eliminar key +- Endpoint de consumo · URL - 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 - 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. +- Espera a que termine; Quorb lo detecta solo. +- Este sistema no permite la instalación asistida. +- Está pensada para Windows; puedes instalarlo a mano y Quorb lo detectará. +- Etiqueta - Expandir el menú - Extra +- Falta Codex CLI en este equipo. - Falta completar un paso - Falta conectar - Falta {name} en este equipo. @@ -1448,6 +1468,8 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a - GLM Coding Plan · cuotas de 5 h, semanales y web tools - Gasto - Gasto del mes con clave de admin +- Gasto del mes y de hoy, límite de la key (si tiene) y saldo de la cuenta. +- Gasto mínimo por sub-proveedor (USD) - Glow de peligro - Go / Go Plus · cuotas de 5 h, semanal y ciclo de facturación - Guarda la cuenta para comprobar que funciona. @@ -1459,12 +1481,15 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a - Guardar cambios - 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. - Hay una versión nueva: {version}. - Historial - Historial de consumo +- Hoy - Icono de bandeja - Idioma +- 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 @@ -1472,47 +1497,71 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a - Inicio al iniciar sesión guardado - Inicio al iniciar sesión no guardado: {error} - Instala {name} +- Instalación cancelada. - Instalando +- Instalando Codex CLI… +- Instalando… +- Instalar Codex - Instalar y reiniciar - Izquierda +- 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. +- La instalación no crea la sesión: usa «Iniciar sesión» cuando quieras. +- 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. - Listo - Llegan firmadas desde +- Los ocultaste desde su panel en la tira. Pulsa para volver a mostrarlos. - 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 cada sub-proveedor como su propia cuenta - Mostrar en el widget +- Mostrar gasto del mes - Mostrar resets en los círculos - Mostrar u ocultar la tira - Ninguno - Ningún archivo - No funciona +- No muestra un sub-orbe hasta que su gasto en el periodo supere esta cifra. 0 = cualquiera que gaste. +- No se cambió nada. Puedes intentarlo de nuevo. +- No se detecta Node.js y npm. - No se pudo actualizar Quorb - No se pudo comprobar ahora: {error}. Vuelve a intentarlo. +- No se pudo consultar el instalador. - No se pudo copiar - No se pudo determinar el modo de actualización. Reinicia Quorb para reintentar. - No se pudo guardar el idioma +- No se pudo instalar Codex CLI. - No se pudo leer «Arrancar al iniciar sesión»: - Nombre - Notificaciones - Notificación del sistema - Ocultar sola - Opacidad de la tira y el panel +- Opcional: pinta la barra de gasto del periodo frente al tope que pongas. - Opus - Otra cantidad +- Pago por uso · API oficial +- Pago por uso · Admin key - Pantalla completa +- Paquete fijado: - Para WSL: - Para cambiar el orden de los anillos, arrástralos en la tira. +- Pasarela local · uso agregado - Pasarela local · uso y gasto agregados - Pedir un proveedor - Pega aquí la key - Pega el token de esta cuenta - Pequeño +- Periodo de uso - Periodo hasta +- Periodo y desglose - Personalización no aplicada - Peticiones - Peticiones premium del mes @@ -1525,13 +1574,16 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a - Posicionamiento de la ventana - Posición en el borde - Presupuesto +- Presupuesto (USD) - Presupuesto mensual (opcional) - Principal - Pro / Max · sesión de 5 h, semanal y por modelo - Probar +- Probar la API de NaN con la key - Proveedores - Proyecto - Puede ser de otra cuenta o de la misma con otro límite. +- Puedes reintentarlo. - 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. @@ -1540,11 +1592,18 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a - Quorb consulta el servidor local que arranca el IDE de escritorio en este equipo. Solo se lee con ese IDE abierto; no se guarda ningún token. El agy CLI en macOS no expone el token CSRF, así que esa sesión no se puede leer. - 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 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. +- Quorb puede instalarlo desde aquí: Windows pedirá confirmación antes de instalar nada. +- Quorb solo comprueba que existan; la versión (Node.js 22 o superior) la valida el instalador al confirmar. Instálalos y vuelve a comprobar. +- Quorb solo consulta el estado del equipo. +- Quorb ya detecta el CLI en este equipo. - Quorb {version} disponible +- Qué instala exactamente - Qué pasa al superar un límite y cuando se reinicia. - Qué se consulta y cómo se conecta cada proveedor. - Rango del historial +- Rango que muestra el orbe y su historial. - Reintentar - Reintentar mostrar Quorb - Renovar sesión @@ -1560,6 +1619,8 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a - 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 ignoran los scripts de ciclo de vida del paquete y se valida la versión real antes de darlo por bueno. +- Se llama con tu key como Bearer y debe devolver JSON. - Se reinicia {when} - Se ve en la tira junto al proveedor. - Secciones de ajustes @@ -1571,8 +1632,10 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a - 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 GLM 5.3 (cuenta por periodo de Stripe) cuando usas la API key. - Solo para vídeos. - Sonnet +- Suborbes ocultos de 9Router - Subscription Key del plan - SuperGrok · uso semanal de créditos del CLI - Suscripciones @@ -1582,15 +1645,18 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a - Suscripción · Go / Go Plus - Suscripción · IDE de escritorio - Suscripción · OAuth +- Suscripción · cuenta o API key - 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. - También puedes arrastrarla desde su asa: se pega al borde más cercano. +- Te hemos enviado un enlace. Cópialo (sin abrirlo) y pégalo aquí. - Tiempo transcurrido de la ventana - Tienes cambios sin guardar: la comprobación usa lo guardado. - Tira +- Todo - Token de GitHub (fine-grained) - Token de GitHub de esta cuenta (fine-grained) - Token de la cuenta @@ -1612,11 +1678,15 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a - Usuario de GitHub - Usuario y plan de GitHub - Vacío = +- Ver respuestas de /v1/models y /v1/usage - Versión actual - Versión instalada - Versión instalada, actualizaciones automáticas y el proyecto. - Vista previa a {size} px +- Volver - Volver a abrir el login +- Volver a comprobar +- Vuelve a comprobar el estado. - Vídeo o imagen animada - apuntando a ella. Si la comprobación pide renovar la sesión, «{reopen}» abre de nuevo el CLI. - caché @@ -1625,6 +1695,7 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a - con el permiso «Plan» (solo lectura). - con límites: - dentro de ella. +- desde el registro oficial de npm. No se instala «la última». - dos credenciales distintas - el gestor no colocó la tira donde se pidió - el gestor no concedió el tamaño @@ -1639,6 +1710,8 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a - la ventana nativa todavía no es visible - máx. {value} - no se pudo leer la geometría nativa +- p. ej. /usage/quota_tokens +- p. ej. /usage/used_tokens - reinicia {when} - saldo comunicado por Codex; no son reinicios automáticos - saldo comunicado por z.ai; cada reset vence y se usa manualmente @@ -1646,6 +1719,8 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a - sin límites conocidos: - su CLI - una terminal +- 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. - {count} resets disponibles - {day}: {value} tokens - {label} se agota{when}, antes del reinicio. @@ -1655,6 +1730,10 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a - · estás en la última versión - · oculta en el widget - · {count} cuentas +- Últimas 24 h +- Últimos 30 días +- Últimos 60 días +- Últimos 7 días - •••••••• (configurada) ## src/lib/accounts.ts diff --git a/src/Settings-api-panels-i18n.test.tsx b/src/Settings-api-panels-i18n.test.tsx new file mode 100644 index 0000000..8f23b40 --- /dev/null +++ b/src/Settings-api-panels-i18n.test.tsx @@ -0,0 +1,335 @@ +// Corte visible de i18n para los paneles de APIs y las conexiones NaN/9Router +// (#66, T2c slice 2 C2): OpenRouter, NaN Builders, 9Router, Anthropic API y +// OpenAI API —incluidos NanConnect, NanProbe, NineRouterConnect y CodexInstall— +// 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 { CodexInstall } from './components/CodexInstall'; +import type { CodexInstallController, CodexInstallState, CodexSetupStatus } from './lib/codex-install'; +import type { AppConfig, Locale } from './lib/types'; +import { + AdminApiPanel, + NanConnect, + NanPanel, + NineRouterConnect, + NineRouterPanel, + OpenRouterPanel, +} 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 APIs. +const base = { + hiddenProviders: [], + openrouter: { enabled: true }, + nan: { enabled: true, plan: 'community', baseUrl: 'https://api.nan.builders', billingDay: 1, usageUrl: null, usedPointer: null, limitPointer: null, usageLabel: null }, + nineRouter: { enabled: true, period: '7d', showSubProviders: false, minSpendUsd: 0, monthlyBudgetUsd: null, baseUrl: 'http://localhost:20128' }, + anthropicApi: { enabled: true, monthlyBudgetUsd: null }, + openaiApi: { enabled: true, monthlyBudgetUsd: null }, + 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, +}); + +const common = (locale: Locale, cfg: AppConfig = base) => ({ + locale, + cfg, + secrets: {}, + set: noop, + setVisibility: noop, + loadSecrets: noop, + healthFor: healthFor(locale), +}); + +const openrouter = (locale: Locale) => renderToStaticMarkup(); +const nanPanel = (locale: Locale) => renderToStaticMarkup(); +const nineRouter = (locale: Locale, cfg: AppConfig = base) => renderToStaticMarkup(); +const adminApi = (locale: Locale, tab: 'anthropic_api' | 'openai_api') => renderToStaticMarkup(); +const nanConnect = (locale: Locale, connected: boolean) => renderToStaticMarkup(); +const nineConnect = (locale: Locale, connected: boolean) => + renderToStaticMarkup(); + +// Estado inyectable del controlador de CodexInstall: cada vista se fuerza sin montar React. +const codexState = (patch: Partial): CodexInstallState => ({ + status: null, + reading: false, + installing: false, + outcome: null, + message: null, + error: null, + errorFrom: null, + ...patch, +}); +const codexStatus = (patch: Partial): CodexSetupStatus => ({ + supported: true, + busy: false, + nodeAvailable: true, + installed: false, + package: '', + message: null, + outcome: null, + ...patch, +}); +const controllerFor = (state: CodexInstallState): CodexInstallController => ({ + getState: () => state, + subscribe: () => () => {}, + setVisible: noop, + start: noop, + stop: noop, + refresh: async () => state, + requestInstall: async () => ({ ok: true, outcome: 'installed' }), +}); +const codex = (locale: Locale, state: CodexInstallState) => renderToStaticMarkup(); + +describe('API provider panels (SSR, i18n)', () => { + it('renders the Spanish literals of OpenRouter copied verbatim from the current code', () => { + expectAll( + openrouter('es'), + [ + 'Pago por uso · API oficial', + 'Mostrar en el widget', + 'Gasto del mes y de hoy, límite de la key (si tiene) y saldo de la cuenta.', + 'aria-label="Cómo conectar la cuenta"', + 'API key', + 'Créala en', + 'openrouter.ai › Keys', + 'Comprobando la conexión…', + ], + 'es openrouter', + ); + }); + + it('renders OpenRouter in English and drops the Spanish literals', () => { + const html = openrouter('en'); + expectAll( + html, + ['Pay-as-you-go · Official API', 'Show in the widget', 'This month's and today's spend, key limit (if any) and account balance.', 'aria-label="How to connect the account"', 'Create it at', 'openrouter.ai › Keys'], + 'en openrouter', + ); + expectNone(html, ['Pago por uso', 'Mostrar en el widget', 'Cómo conectar la cuenta', 'Créala en', 'Gasto del mes'], 'en openrouter'); + }); + + it('renders the Spanish literals of NaN (panel, connect, probe and advanced) copied verbatim', () => { + expectAll( + nanPanel('es'), + [ + 'Suscripción · cuenta o API key', + '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', + '/v1/usage', + 'Conecta tu cuenta o pega una API key', + 'Conectar tu cuenta (recomendado)', + 'placeholder="tu@correo.com"', + 'Enviar enlace', + '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.', + 'API key (alternativa)', + 'cloud.nan.builders', + '› API Keys', + 'nan_community', + '', + 'Avanzado · URL, facturación y endpoint de consumo', + 'Base URL', + 'Día de inicio de facturación', + 'Solo para GLM 5.3 (cuenta por periodo de Stripe) cuando usas la API key.', + 'Probar la API de NaN con la key', + 'Ver respuestas de /v1/models y /v1/usage', + 'Endpoint de consumo · URL', + 'JSON Pointer · usado', + 'p. ej. /usage/used_tokens', + 'JSON Pointer · límite', + 'p. ej. /usage/quota_tokens', + 'Etiqueta', + 'placeholder="Consumo"', + ], + 'es nan', + ); + }); + + it('renders the NaN panel in English and drops the Spanish literals', () => { + const html = nanPanel('en'); + expectAll( + html, + [ + 'Subscription · account or API key', + 'Connect your account or paste an API key', + 'Connect your account (recommended)', + 'placeholder="you@email.com"', + 'Send link', + 'API key (alternative)', + 'Advanced · URL, billing and usage endpoint', + 'Billing start day', + 'Test the NaN API with your key', + 'See /v1/models and /v1/usage responses', + 'Usage endpoint · URL', + 'JSON Pointer · used', + 'e.g. /usage/used_tokens', + 'JSON Pointer · limit', + 'Label', + 'placeholder="Usage"', + ], + 'en nan', + ); + expectNone(html, ['Suscripción · cuenta o API key', 'Conecta tu cuenta o pega una API key', 'Día de inicio de facturación', 'Etiqueta', 'Probar la API de NaN', 'p. ej.'], 'en nan'); + }); + + it('renders the Spanish literals of the connected NaN account copied verbatim', () => { + expectAll(nanConnect('es', true), ['Cuenta de NaN Cloud conectada', 'Desconectar'], 'es nan conectada'); + }); + + it('renders the connected NaN account in English', () => { + const html = nanConnect('en', true); + expectAll(html, ['NaN Cloud account connected', 'Disconnect'], 'en nan conectada'); + expectNone(html, ['Cuenta de NaN Cloud conectada', 'Desconectar'], 'en nan conectada'); + }); + + it('renders the Spanish literals of 9Router (panel, connect, periods and hidden sub-orbs) copied verbatim', () => { + const html = nineRouter('es'); + expectAll( + html, + [ + 'Pasarela local · uso agregado', + 'Quorb lee el uso y el gasto que 9Router ya registra en tu equipo: peticiones, tokens y coste por proveedor del periodo.', + 'aria-label="Cómo conectar 9Router"', + 'Conecta tu dashboard local', + 'Conectar con el dashboard', + 'Contraseña del dashboard', + 'Guardar y probar', + 'La contraseña del dashboard de 9Router (por defecto «123456» hasta que la cambies). Se guarda en el almacén seguro del sistema.', + 'Periodo y desglose', + 'Periodo de uso', + 'Rango que muestra el orbe y su historial.', + '', + '', + 'Últimos 7 días', + '', + '', + '', + 'Mostrar cada sub-proveedor como su propia cuenta', + 'Gasto mínimo por sub-proveedor (USD)', + 'Presupuesto (USD)', + 'Avanzado · dirección del servidor', + 'Dónde escucha 9Router (por defecto http://localhost:20128).', + ], + 'es 9router', + ); + // Suborbes ocultos: solo se pintan con ids ocultos de 9Router. + expectAll( + nineRouter('es', { ...base, hiddenProviders: ['nine_router:kiro'] } as AppConfig), + ['Suborbes ocultos de 9Router', '9Router · kiro · mostrar', 'Los ocultaste desde su panel en la tira. Pulsa para volver a mostrarlos.'], + 'es 9router ocultos', + ); + void html; + }); + + it('renders the 9Router panel in English and drops the Spanish literals', () => { + const html = nineRouter('en'); + expectAll( + html, + [ + 'Local gateway · aggregated usage', + 'aria-label="How to connect 9Router"', + 'Connect your local dashboard', + 'Connect to the dashboard', + 'Dashboard password', + 'Save and test', + 'Period and breakdown', + 'Usage period', + '', + '', + '', + 'Show each sub-provider as its own account', + 'Minimum spend per sub-provider (USD)', + 'Budget (USD)', + 'Advanced · server address', + 'Where 9Router listens (http://localhost:20128 by default).', + ], + 'en 9router', + ); + expectNone(html, ['Pasarela local', 'Conecta tu dashboard local', 'Periodo de uso', 'Presupuesto (USD)', 'Últimos'], 'en 9router'); + }); + + it('renders the connected 9Router account in Spanish and English', () => { + expectAll( + nineConnect('es', true), + ['9Router conectado · volver a conectar', 'placeholder="•••••••• (configurada)"', 'Quitar', 'title="Desconectar 9Router"'], + 'es 9router conectado', + ); + const html = nineConnect('en', true); + expectAll(html, ['9Router connected · reconnect', 'placeholder="•••••••• (configured)"', 'Remove', 'title="Disconnect 9Router"'], 'en 9router conectado'); + expectNone(html, ['9Router conectado', 'Quitar', 'configurada)'], 'en 9router conectado'); + }); + + it('renders the Spanish literals of the Anthropic API and OpenAI API panels copied verbatim', () => { + expectAll( + adminApi('es', 'anthropic_api'), + ['Pago por uso · Admin key', 'Anthropic API', 'Mostrar gasto del mes', 'Admin key', 'Admin API key (sk-ant-admin…)', 'Las APIs de coste no admiten OAuth: hace falta una', '(solo lectura de la organización).', 'Admin key (solo lectura de la organización).', 'Presupuesto mensual (opcional)', 'USD al mes'], + 'es anthropic', + ); + expectAll(adminApi('es', 'openai_api'), ['OpenAI API', 'class="eg-field__label">Admin key<', 'Créala en', 'Organization › Admin keys'], 'es openai'); + }); + + it('renders the Anthropic API and OpenAI API panels in English and drops the Spanish literals', () => { + const anthropic = adminApi('en', 'anthropic_api'); + expectAll(anthropic, ['Pay-as-you-go · Admin key', 'Show month spend', 'Cost APIs do not support OAuth: you need an', '(organization read-only).', 'Admin key (organization read-only).', 'Monthly budget (optional)', 'USD per month'], 'en anthropic'); + expectNone(anthropic, ['Pago por uso', 'Mostrar gasto del mes', 'Presupuesto mensual', 'hace falta una'], 'en anthropic'); + const openai = adminApi('en', 'openai_api'); + expectAll(openai, ['Pay-as-you-go · Admin key', 'Create it at', 'Organization › Admin keys'], 'en openai'); + expectNone(openai, ['Créala en', 'Mostrar gasto del mes'], 'en openai'); + }); +}); + +describe('CodexInstall notice (SSR, i18n)', () => { + it('renders every Spanish view copied verbatim from the current code', () => { + expectAll(codex('es', codexState({})), ['Comprobando Codex CLI…', 'Quorb solo consulta el estado del equipo.'], 'es loading'); + expectAll(codex('es', codexState({ status: codexStatus({}) })), ['Falta Codex CLI en este equipo.', 'Quorb puede instalarlo desde aquí: Windows pedirá confirmación antes de instalar nada.', 'Instalar Codex', 'Qué instala exactamente'], 'es ready'); + expectAll(codex('es', codexState({ installing: true })), ['Instalando Codex CLI…', 'Instalando…'], 'es installing'); + expectAll(codex('es', codexState({ outcome: 'installed' })), ['Codex CLI instalado.', 'La instalación no crea la sesión: usa «Iniciar sesión» cuando quieras.'], 'es installed fresh'); + expectAll(codex('es', codexState({ status: codexStatus({ installed: true }) })), ['Codex está instalado; revisa tu comando de login.', 'Quorb ya detecta el CLI en este equipo.'], 'es installed stale'); + expectAll(codex('es', codexState({ outcome: 'cancelled' })), ['Instalación cancelada.', 'No se cambió nada. Puedes intentarlo de nuevo.', 'Reintentar'], 'es cancelled'); + expectAll(codex('es', codexState({ status: codexStatus({ busy: true }) })), ['El instalador está trabajando en otra ventana.', 'Espera a que termine; Quorb lo detecta solo.'], 'es busy'); + expectAll(codex('es', codexState({ status: codexStatus({ supported: false }) })), ['Este sistema no permite la instalación asistida.', 'Está pensada para Windows; puedes instalarlo a mano y Quorb lo detectará.', 'Volver a comprobar', 'Copiar orden'], 'es unsupported'); + expectAll(codex('es', codexState({ status: codexStatus({ nodeAvailable: false }) })), ['No se detecta Node.js y npm.', 'Quorb solo comprueba que existan; la versión (Node.js 22 o superior) la valida el instalador al confirmar. Instálalos y vuelve a comprobar.'], 'es no-node'); + expectAll( + codex('es', codexState({ error: 'boom', errorFrom: 'read' })), + ['No se pudo consultar el instalador.', 'boom', 'Vuelve a comprobar el estado.'], + 'es error read', + ); + expectAll(codex('es', codexState({ error: 'boom', errorFrom: 'install' })), ['No se pudo instalar Codex CLI.', 'Puedes reintentarlo.'], 'es error install'); + expectAll( + codex('es', codexState({ status: codexStatus({}) })), + ['Paquete fijado: @openai/codex@0.162.1 desde el registro oficial de npm. No se instala «la última».', 'Carpeta de destino: %USERPROFILE%\\…\\tools\\codex-0.162.1, dentro de la carpeta de Quorb. No se toca el npm global ni las variables de entorno.', 'Se ignoran los scripts de ciclo de vida del paquete y se valida la versión real antes de darlo por bueno.'], + 'es details', + ); + }); + + it('renders every view in English and drops the Spanish literals', () => { + const loading = codex('en', codexState({})); + expectAll(loading, ['Checking Codex CLI…', 'Quorb only checks the state of this machine.'], 'en loading'); + expectNone(loading, ['Comprobando Codex CLI', 'consulta el estado'], 'en loading'); + const ready = codex('en', codexState({ status: codexStatus({}) })); + expectAll(ready, ['Codex CLI is missing on this machine.', 'Install Codex', 'What exactly it installs'], 'en ready'); + expectNone(ready, ['Falta Codex CLI', 'Instalar Codex<'], 'en ready'); + expectAll(codex('en', codexState({ installing: true })), ['Installing Codex CLI…', 'Installing…'], 'en installing'); + expectAll(codex('en', codexState({ outcome: 'cancelled' })), ['Installation cancelled.', 'Nothing was changed. You can try again.', 'Retry'], 'en cancelled'); + expectAll(codex('en', codexState({ status: codexStatus({ supported: false }) })), ['This system does not support assisted installation.', 'Check again', 'Copy command'], 'en unsupported'); + expectAll(codex('en', codexState({ status: codexStatus({ nodeAvailable: false }) })), ['Node.js and npm are not detected.'], 'en no-node'); + expectAll(codex('en', codexState({ error: 'boom', errorFrom: 'read' })), ['The installer could not be queried.', 'boom', 'Check the state again.'], 'en error read'); + expectAll(codex('en', codexState({ error: 'boom', errorFrom: 'install' })), ['Codex CLI could not be installed.', 'You can retry.'], 'en error install'); + }); +}); diff --git a/src/Settings.tsx b/src/Settings.tsx index 34f1e18..d3eb257 100644 --- a/src/Settings.tsx +++ b/src/Settings.tsx @@ -240,7 +240,7 @@ export function SecretInput({ id, label, configured, onSaved, hint, locale = 'es } /** Conectar NaN Cloud como la CLI oficial: enlace de un solo uso enviado al correo. */ -function NanConnect({ connected, onChange, account }: { connected: boolean; onChange: () => void; account?: string }) { +export function NanConnect({ locale = 'es', connected, onChange, account }: { locale?: Locale; connected: boolean; onChange: () => void; account?: string }) { const [email, setEmail] = useState(''); const [link, setLink] = useState(''); const [step, setStep] = useState<'email' | 'link'>('email'); @@ -262,7 +262,7 @@ function NanConnect({ connected, onChange, account }: { connected: boolean; onCh if (connected) { return (
- Cuenta de NaN Cloud conectada{msg ? ` · ${msg}` : ''} + {t(locale, 'settings.providers.nanConnect.connectedAccount')}{msg ? ` · ${msg}` : ''}
); @@ -281,10 +281,10 @@ function NanConnect({ connected, onChange, account }: { connected: boolean; onCh return (
- Conectar tu cuenta (recomendado) + {t(locale, 'settings.providers.nanConnect.label')} {step === 'email' ? (
- setEmail(e.target.value)} /> + setEmail(e.target.value)} />
) : ( @@ -308,34 +308,31 @@ function NanConnect({ connected, onChange, account }: { connected: boolean; onCh onClick={() => run(async () => { const who = await api.nanLoginComplete(link, account); - setMsg(`Conectado como ${who}`); + setMsg(t(locale, 'settings.providers.nanConnect.connectedAs', { who })); setLink(''); onChange(); }) } > - Conectar + {t(locale, 'settings.providers.connect')}
)} - - {msg ?? - '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.'} - + {msg ?? t(locale, 'settings.providers.nanConnect.hint')} ); } /** Muestra las respuestas en crudo de la API de NaN con tu key (para depurar). */ -function NanProbe() { +export function NanProbe({ locale = 'es' }: { locale?: Locale } = {}) { const [out, setOut] = useState(null); const [busy, setBusy] = useState(false); return (
- Probar la API de NaN con la key + {t(locale, 'settings.providers.nanProbe.title')} {out &&
{out}
}
@@ -358,7 +355,7 @@ function NanProbe() { } /** Contraseña del dashboard de 9Router: se guarda y se prueba el login a la vez. */ -function NineRouterConnect({ connected, baseUrl, onChange }: { connected: boolean; baseUrl: string; onChange: () => void }) { +export function NineRouterConnect({ locale = 'es', connected, baseUrl, onChange }: { locale?: Locale; connected: boolean; baseUrl: string; onChange: () => void }) { const [password, setPassword] = useState(''); const [busy, setBusy] = useState(false); const [msg, setMsg] = useState(null); @@ -370,12 +367,12 @@ function NineRouterConnect({ connected, baseUrl, onChange }: { connected: boolea try { if (!value.trim()) { await api.nineRouterLogout(); - setMsg('9Router desconectado'); + setMsg(t(locale, 'settings.providers.nineRouterConnect.disconnectedMsg')); } else { // Prueba contra la URL del borrador: si el usuario cambió «Base URL» sin // guardar, el login debe ir a esa, no a la persistida (regresión punto 4). setOut(await api.nineRouterLogin(value, baseUrl)); - setMsg('Conectado'); + setMsg(t(locale, 'settings.providers.nineRouterConnect.connectedMsg')); } setPassword(''); onChange(); @@ -388,28 +385,26 @@ function NineRouterConnect({ connected, baseUrl, onChange }: { connected: boolea return (
- {connected ? '9Router conectado · volver a conectar' : 'Conectar con el dashboard'} + {connected ? t(locale, 'settings.providers.nineRouterConnect.connectedSummary') : t(locale, 'settings.providers.nineRouterConnect.connectSummary')}
setPassword(e.target.value)} /> {connected && ( - )}
- - {msg ?? 'La contraseña del dashboard de 9Router (por defecto «123456» hasta que la cambies). Se guarda en el almacén seguro del sistema.'} - + {msg ?? t(locale, 'settings.providers.nineRouterConnect.hint')} {out &&
{out}
}
); @@ -894,7 +889,7 @@ export function CliConnectView({
  • {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' ? : } + {provider === 'codex' ? : }
  • )}
  • @@ -1063,7 +1058,7 @@ export function AccountEditor({ <>
  • {t(locale, 'settings.providers.accountEditor.nanTitle')} - +
  • @@ -2133,6 +2128,209 @@ export function OpenCodeGoPanel({ locale, cfg, secrets, setVisibility, loadSecre ); } +/** Paneles de APIs y conexiones NaN/9Router (T2c slice 2 C2, #66). Extraídos de + * `Settings()` para poder renderizarlos en pruebas de i18n. Los títulos son marcas + * y no se traducen; tampoco `Base URL` ni los rótulos de consolas de terceros. */ +export function OpenRouterPanel({ locale, cfg, secrets, setVisibility, loadSecrets, healthFor }: PanelProps) { + return ( +
    + setVisibility('openrouter', v)} /> +

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

    +
      +
    1. + {t(locale, 'settings.providers.apiKey')} + {t(locale, 'settings.providers.panels.createApiKeyIn')} openrouter.ai › Keys.} + /> +
    2. + +
    +
    + ); +} + +export function NanPanel({ locale, cfg, secrets, set, setVisibility, loadSecrets, healthFor }: PanelProps) { + return ( +
    + setVisibility('nan', v)} /> +

    + {t(locale, 'settings.providers.panels.nan.noteBefore')} /v1/usage {t(locale, 'settings.providers.panels.nan.noteAfter')} Docs +

    +
      +
    1. + {t(locale, 'settings.providers.panels.nan.stepTitle')} + + {t(locale, 'settings.providers.panels.createApiKeyIn')} cloud.nan.builders › API Keys} + /> +
    2. +
    3. + {t(locale, 'settings.providers.planLabel')} + + + +
    4. + +
    +
    + {t(locale, 'settings.providers.panels.nan.advancedSummary')} + + set('nan', { baseUrl: e.target.value })} /> + + + set('nan', { billingDay: Number(e.target.value) || 1 })} /> + + + + set('nan', { usageUrl: strOrNull(e.target.value) })} /> + +
    + + set('nan', { usedPointer: strOrNull(e.target.value) })} /> + + + set('nan', { limitPointer: strOrNull(e.target.value) })} /> + +
    + + set('nan', { usageLabel: strOrNull(e.target.value) })} /> + +
    +
    + ); +} + +export function NineRouterPanel({ locale, cfg, secrets, set, setVisibility, loadSecrets, healthFor }: PanelProps) { + return ( +
    + setVisibility('nine_router', v)} /> +

    + {t(locale, 'settings.providers.panels.nineRouter.noteBefore')} 9Router {t(locale, 'settings.providers.panels.nineRouter.noteAfter')} +

    +
      +
    1. + {t(locale, 'settings.providers.panels.nineRouter.connectTitle')} + +
    2. +
    3. + {t(locale, 'settings.providers.panels.nineRouter.periodTitle')} + + + + set('nineRouter', { showSubProviders: v })} + /> + + set('nineRouter', { minSpendUsd: Math.max(0, Number(e.target.value) || 0) })} + /> + + + set('nineRouter', { monthlyBudgetUsd: strOrNull(e.target.value) == null ? null : Number(e.target.value) })} + /> + +
    4. + +
    +
    + {t(locale, 'settings.providers.panels.nineRouter.advancedSummary')} + + set('nineRouter', { baseUrl: e.target.value })} /> + +
    + {(() => { + // Suborbes de 9Router ocultados desde el widget: se pueden recuperar aquí. + const hidden = hiddenNineRouterIds(cfg.hiddenProviders); + if (hidden.length === 0) return null; + return ( +
    + {t(locale, 'settings.providers.panels.nineRouter.hiddenTitle')} +
    + {hidden.map((id) => ( + + ))} +
    + {t(locale, 'settings.providers.panels.nineRouter.hiddenHint')} +
    + ); + })()} +
    + ); +} + +export function AdminApiPanel({ locale, cfg, secrets, set, setVisibility, loadSecrets, healthFor, tab }: PanelProps & { tab: 'anthropic_api' | 'openai_api' }) { + const k = tab === 'anthropic_api' ? 'anthropicApi' : 'openaiApi'; + const secret: SecretKey = tab === 'anthropic_api' ? 'anthropic_admin_key' : 'openai_admin_key'; + return ( +
    + setVisibility(tab, v)} /> +
      +
    1. + {t(locale, 'settings.providers.accountEditor.adminKey')} + + {t(locale, 'settings.providers.panels.adminApi.anthropicHintBefore')} Admin key{' '} + {t(locale, 'settings.providers.panels.adminApi.anthropicHintAfter')} + + ) : ( + <>{t(locale, 'settings.providers.panels.createApiKeyIn')} Organization › Admin keys. + ) + } + /> +
    2. +
    3. + {t(locale, 'settings.providers.accountEditor.budgetTitle')} + + set(k, { monthlyBudgetUsd: numOrNull(e.target.value) })} /> + +
    4. + +
    +
    + ); +} + export function Settings() { const appName = useAppName(); @@ -2631,207 +2829,17 @@ export function Settings() { {tab === 'copilot' && } {tab === 'minimax' && } - {tab === 'openrouter' && ( -
    - setVisibility('openrouter', v)} /> -

    Gasto del mes y de hoy, límite de la key (si tiene) y saldo de la cuenta.

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

    - 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 /v1/usage y las cuotas publicadas. Docs -

    -
      -
    1. - Conecta tu cuenta o pega una API key - - Créala en cloud.nan.builders › API Keys} - /> -
    2. -
    3. - Plan - - - -
    4. - -
    -
    - Avanzado · URL, facturación y endpoint de consumo - - set('nan', { baseUrl: e.target.value })} /> - - - set('nan', { billingDay: Number(e.target.value) || 1 })} /> - - - - set('nan', { usageUrl: strOrNull(e.target.value) })} /> - -
    - - set('nan', { usedPointer: strOrNull(e.target.value) })} /> - - - set('nan', { limitPointer: strOrNull(e.target.value) })} /> - -
    - - set('nan', { usageLabel: strOrNull(e.target.value) })} /> - -
    -
    - )} + {tab === 'nan' && } - {tab === 'nine_router' && ( -
    - setVisibility('nine_router', v)} - /> -

    - Quorb lee el uso y el gasto que 9Router 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. -

    -
      -
    1. - Conecta tu dashboard local - -
    2. -
    3. - Periodo y desglose - - - - set('nineRouter', { showSubProviders: v })} - /> - - set('nineRouter', { minSpendUsd: Math.max(0, Number(e.target.value) || 0) })} - /> - - - set('nineRouter', { monthlyBudgetUsd: strOrNull(e.target.value) == null ? null : Number(e.target.value) })} - /> - -
    4. - -
    -
    - Avanzado · dirección del servidor - - set('nineRouter', { baseUrl: e.target.value })} /> - -
    - {(() => { - // Suborbes de 9Router ocultados desde el widget: se pueden recuperar aquí. - const hidden = hiddenNineRouterIds(cfg.hiddenProviders); - if (hidden.length === 0) return null; - return ( -
    - Suborbes ocultos de 9Router -
    - {hidden.map((id) => ( - - ))} -
    - Los ocultaste desde su panel en la tira. Pulsa para volver a mostrarlos. -
    - ); - })()} -
    - )} + {tab === 'nine_router' && } - {(tab === 'anthropic_api' || tab === 'openai_api') && - (() => { - const k = tab === 'anthropic_api' ? 'anthropicApi' : 'openaiApi'; - const secret: SecretKey = tab === 'anthropic_api' ? 'anthropic_admin_key' : 'openai_admin_key'; - return ( -
    - setVisibility(tab, v)} /> -
      -
    1. - Admin key - Las APIs de coste no admiten OAuth: hace falta una Admin key (solo lectura de la organización). - ) : ( - <>Créala en Organization › Admin keys. - ) - } - /> -
    2. -
    3. - Presupuesto mensual (opcional) - - set(k, { monthlyBudgetUsd: numOrNull(e.target.value) })} /> - -
    4. - -
    -
    - ); - })()} + {(tab === 'anthropic_api' || tab === 'openai_api') && } )} diff --git a/src/components/CodexInstall.tsx b/src/components/CodexInstall.tsx index 78635e1..64c8d02 100644 --- a/src/components/CodexInstall.tsx +++ b/src/components/CodexInstall.tsx @@ -1,14 +1,16 @@ import { useEffect, useSyncExternalStore } from 'react'; import { CODEX_PACKAGE, codexInstallActions, codexInstallView, type CodexInstallAction, type CodexInstallController } from '../lib/codex-install'; +import { t } from '../i18n'; import { ownedHomeDir, useAppName } from '../lib/app-identity'; +import type { Locale } from '../lib/types'; /** * Sustituye al aviso de copia solo para Codex: ofrece la instalación asistida * (con confirmación nativa del backend) y el rechequeo. Los demás proveedores * mantienen su flujo de copiar e instalar a mano. */ -export function CodexInstall({ controller, onRecheck }: { controller: CodexInstallController; onRecheck: () => void }) { +export function CodexInstall({ locale = 'es', controller, onRecheck }: { locale?: Locale; controller: CodexInstallController; onRecheck: () => void }) { const appName = useAppName(); const targetFolder = `%USERPROFILE%\\${ownedHomeDir(appName)}\\tools\\codex-0.162.1`; const state = useSyncExternalStore(controller.subscribe, controller.getState, controller.getState); @@ -38,102 +40,102 @@ export function CodexInstall({ controller, onRecheck }: { controller: CodexInsta {view.kind === 'loading' && ( <> - Comprobando Codex CLI… Quorb solo consulta el estado del equipo. + {t(locale, 'settings.providers.codexInstall.loadingBold')} {t(locale, 'settings.providers.codexInstall.loadingRest')} )} {view.kind === 'ready' && ( <> - Falta Codex CLI en este equipo. Quorb puede instalarlo desde aquí: Windows pedirá confirmación antes de instalar nada. + {t(locale, 'settings.providers.codexInstall.readyBold')} {t(locale, 'settings.providers.codexInstall.readyRest')} )} {view.kind === 'installing' && ( <> - Instalando Codex CLI… Confirma en la ventana de Windows si aparece. Puedes cancelar desde ahí. + {t(locale, 'settings.providers.codexInstall.installingBold')} {t(locale, 'settings.providers.codexInstall.installingRest')} )} {view.kind === 'installed' && view.justInstalled && ( <> - Codex CLI instalado. La instalación no crea la sesión: usa «Iniciar sesión» cuando quieras. + {t(locale, 'settings.providers.codexInstall.installedBold')} {t(locale, 'settings.providers.codexInstall.installedRest')} )} {view.kind === 'installed' && !view.justInstalled && ( <> - Codex está instalado; revisa tu comando de login. Quorb ya detecta el CLI en este equipo. + {t(locale, 'settings.providers.codexInstall.installedStaleBold')} {t(locale, 'settings.providers.codexInstall.installedStaleRest')} )} {view.kind === 'cancelled' && ( <> - Instalación cancelada. No se cambió nada. Puedes intentarlo de nuevo. + {t(locale, 'settings.providers.codexInstall.cancelledBold')} {t(locale, 'settings.providers.codexInstall.cancelledRest')} )} {view.kind === 'busy' && ( <> - El instalador está trabajando en otra ventana. Espera a que termine; Quorb lo detecta solo. + {t(locale, 'settings.providers.codexInstall.busyBold')} {t(locale, 'settings.providers.codexInstall.busyRest')} )} {view.kind === 'unsupported' && ( <> - Este sistema no permite la instalación asistida. Está pensada para Windows; puedes instalarlo a mano y Quorb lo detectará. + {t(locale, 'settings.providers.codexInstall.unsupportedBold')} {t(locale, 'settings.providers.codexInstall.unsupportedRest')} )} {view.kind === 'no-node' && ( <> - No se detecta Node.js y npm. Quorb solo comprueba que existan; la versión (Node.js 22 o superior) la valida el instalador al - confirmar. Instálalos y vuelve a comprobar. + {t(locale, 'settings.providers.codexInstall.noNodeBold')} {t(locale, 'settings.providers.codexInstall.noNodeRest')} )} {view.kind === 'error' && view.origin === 'read' && ( <> - No se pudo consultar el instalador. {view.message} Vuelve a comprobar el estado. + {t(locale, 'settings.providers.codexInstall.errorReadBold')} {view.message} {t(locale, 'settings.providers.codexInstall.errorReadRest')} )} {view.kind === 'error' && view.origin !== 'read' && ( <> - No se pudo instalar Codex CLI. {view.message} Puedes reintentarlo. + {t(locale, 'settings.providers.codexInstall.errorInstallBold')} {view.message} {t(locale, 'settings.providers.codexInstall.errorInstallRest')} )} {can('install') && ( )} {view.kind === 'installing' && ( )} {can('retry') && ( )} {can('recheck') && ( )} {can('copy') && ( )}
    - Qué instala exactamente + {t(locale, 'settings.providers.codexInstall.detailsSummary')}
    • - Paquete fijado: {CODEX_PACKAGE} desde el registro oficial de npm. No se instala «la última». + {t(locale, 'settings.providers.codexInstall.detailsPinnedBefore')} + {CODEX_PACKAGE} + {t(locale, 'settings.providers.codexInstall.detailsPinnedAfter')}
    • - Carpeta de destino: {targetFolder}, dentro de la carpeta de Quorb. No se toca el npm global ni las variables de entorno. -
    • -
    • Se ignoran los scripts de ciclo de vida del paquete y se valida la versión real antes de darlo por bueno.
    • -
    • - Antes de instalar, Quorb solo comprueba que Node.js y npm existan (sin ejecutarlos); la versión mínima se valida al confirmar la - instalación. No se instala Node ni se piden permisos de administrador. + {t(locale, 'settings.providers.codexInstall.detailsFolderBefore')} + {targetFolder} + {t(locale, 'settings.providers.codexInstall.detailsFolderAfter')}
    • +
    • {t(locale, 'settings.providers.codexInstall.detailsScripts')}
    • +
    • {t(locale, 'settings.providers.codexInstall.detailsNode')}
    diff --git a/src/i18n/en.ts b/src/i18n/en.ts index 6bcf7c7..8e993cd 100644 --- a/src/i18n/en.ts +++ b/src/i18n/en.ts @@ -295,6 +295,73 @@ export const en = { remove: 'Remove this account', removeHint: 'Saving also deletes its keys from the system secure store.', }, + // NaN and 9Router connections and the Codex install notice (T2c slice 2 + // C2, #66). Backend messages (`msg`, `view.message`) are data and are not + // translated. + nanConnect: { + connectedAccount: 'NaN Cloud account connected', + disconnect: 'Disconnect', + label: 'Connect your account (recommended)', + emailPlaceholder: 'you@email.com', + sendLink: 'Send link', + linkSent: 'We have sent you a link. Copy it (without opening it) and paste it here.', + back: 'Back', + connectedAs: 'Connected as {who}', + hint: 'Just like «nan auth login --email»: NaN sends you a single-use link. Do not open it in the browser (it would be spent): copy it and paste it here. The session is saved in the system credential store and gives the same data as your dashboard.', + }, + nanProbe: { + title: 'Test the NaN API with your key', + checking: 'Checking…', + run: 'See /v1/models and /v1/usage responses', + }, + nineRouterConnect: { + connectedSummary: '9Router connected · reconnect', + connectSummary: 'Connect to the dashboard', + placeholderConfigured: '•••••••• (configured)', + placeholderPassword: 'Dashboard password', + saveAndTest: 'Save and test', + removeTitle: 'Disconnect 9Router', + remove: 'Remove', + disconnectedMsg: '9Router disconnected', + connectedMsg: 'Connected', + hint: 'The 9Router dashboard password («123456» by default until you change it). It is saved in the system secure store.', + }, + codexInstall: { + loadingBold: 'Checking Codex CLI…', + loadingRest: 'Quorb only checks the state of this machine.', + readyBold: 'Codex CLI is missing on this machine.', + readyRest: 'Quorb can install it from here: Windows will ask for confirmation before installing anything.', + installingBold: 'Installing Codex CLI…', + installingRest: 'Confirm in the Windows window if it appears. You can cancel from there.', + installedBold: 'Codex CLI installed.', + installedRest: 'The installation does not create the session: use «Sign in» whenever you want.', + installedStaleBold: 'Codex is installed; check your login command.', + installedStaleRest: 'Quorb already detects the CLI on this machine.', + cancelledBold: 'Installation cancelled.', + cancelledRest: 'Nothing was changed. You can try again.', + busyBold: 'The installer is working in another window.', + busyRest: 'Wait for it to finish; Quorb detects it on its own.', + unsupportedBold: 'This system does not support assisted installation.', + unsupportedRest: 'It is designed for Windows; you can install it manually and Quorb will detect it.', + noNodeBold: 'Node.js and npm are not detected.', + noNodeRest: 'Quorb only checks that they exist; the version (Node.js 22 or higher) is validated by the installer on confirmation. Install them and check again.', + errorReadBold: 'The installer could not be queried.', + errorReadRest: 'Check the state again.', + errorInstallBold: 'Codex CLI could not be installed.', + errorInstallRest: 'You can retry.', + install: 'Install Codex', + installing: 'Installing…', + retry: 'Retry', + recheck: 'Check again', + copy: 'Copy command', + detailsSummary: 'What exactly it installs', + detailsPinnedBefore: 'Pinned package: ', + detailsPinnedAfter: ' from the official npm registry. The latest version is not installed.', + detailsFolderBefore: 'Target folder: ', + detailsFolderAfter: ', inside the Quorb folder. The global npm and the environment variables are not touched.', + detailsScripts: 'Package lifecycle scripts are ignored and the real version is validated before accepting it.', + detailsNode: 'Before installing, Quorb only checks that Node.js and npm exist (without running them); the minimum version is validated on confirming the installation. Node is not installed and no administrator permissions are requested.', + }, panels: { stepsAria: 'How to connect the account', kickerOAuth: 'Subscription · OAuth', @@ -364,6 +431,62 @@ export const en = { kicker: 'Subscription · Go / Go Plus', note: 'Quota for the last 5 h, weekly quota and billing-cycle quota of the Go / Go Plus subscription.', }, + // API panels and connections (T2c slice 2 C2, #66). «Base URL», + // third-party console labels and brands are not translated. + createApiKeyIn: 'Create it at', + openrouter: { + kicker: 'Pay-as-you-go · Official API', + note: 'This month\'s and today\'s spend, key limit (if any) and account balance.', + }, + nan: { + kicker: 'Subscription · account or API key', + noteBefore: 'With your account connected you see the same as your dashboard: tokens used against the quota of each model, when it resets and the daily series. Without an account, the API key uses', + noteAfter: 'and the published quotas.', + stepTitle: 'Connect your account or paste an API key', + advancedSummary: 'Advanced · URL, billing and usage endpoint', + billingDayLabel: 'Billing start day', + billingDayHint: 'Only for GLM 5.3 (billed by Stripe period) when using the API key.', + usageEndpointLabel: 'Usage endpoint · URL', + usageEndpointHint: 'It is called with your key as Bearer and must return JSON.', + pointerUsedLabel: 'JSON Pointer · used', + pointerLimitLabel: 'JSON Pointer · limit', + pointerUsedHint: 'e.g. /usage/used_tokens', + pointerLimitHint: 'e.g. /usage/quota_tokens', + usageLabelField: 'Label', + usageLabelPlaceholder: 'Usage', + }, + nineRouter: { + kicker: 'Local gateway · aggregated usage', + noteBefore: 'Quorb reads the usage and spend that', + noteAfter: 'already records on your machine: requests, tokens and cost per provider of the period. Besides the aggregated orb, every provider it has seen (Claude, Kiro, GLM…) appears as its own account.', + stepsAria: 'How to connect 9Router', + connectTitle: 'Connect your local dashboard', + periodTitle: 'Period and breakdown', + periodLabel: 'Usage period', + periodHint: 'Range shown by the orb and its history.', + periodToday: 'Today', + period24h: 'Last 24 h', + period7d: 'Last 7 days', + period30d: 'Last 30 days', + period60d: 'Last 60 days', + periodAll: 'All', + subprovidersToggle: 'Show each sub-provider as its own account', + minSpendLabel: 'Minimum spend per sub-provider (USD)', + minSpendHint: 'A sub-orb is not shown until its period spend exceeds this figure. 0 = show any with spend.', + budgetLabel: 'Budget (USD)', + budgetHint: 'Optional: shows the period spend bar against your cap.', + advancedSummary: 'Advanced · server address', + baseUrlHint: 'Where 9Router listens (http://localhost:20128 by default).', + hiddenTitle: 'Hidden 9Router sub-orbs', + hiddenShow: 'show', + hiddenHint: 'You hid them from their panel in the strip. Click to show them again.', + }, + adminApi: { + kicker: 'Pay-as-you-go · Admin key', + showSpend: 'Show month spend', + anthropicHintBefore: 'Cost APIs do not support OAuth: you need an', + anthropicHintAfter: '(organization read-only).', + }, }, }, }, diff --git a/src/i18n/es.ts b/src/i18n/es.ts index 6d5df3f..a5c3336 100644 --- a/src/i18n/es.ts +++ b/src/i18n/es.ts @@ -314,6 +314,73 @@ export const es = { remove: 'Eliminar esta cuenta', removeHint: 'Al guardar se borran también sus claves del almacén seguro del sistema.', }, + // Conexiones de NaN y 9Router y aviso de instalación de Codex (T2c slice 2 + // C2, #66). Los mensajes del backend (`msg`, `view.message`) son datos y no + // se traducen. + nanConnect: { + connectedAccount: 'Cuenta de NaN Cloud conectada', + disconnect: 'Desconectar', + label: 'Conectar tu cuenta (recomendado)', + emailPlaceholder: 'tu@correo.com', + sendLink: 'Enviar enlace', + linkSent: 'Te hemos enviado un enlace. Cópialo (sin abrirlo) y pégalo aquí.', + back: 'Volver', + connectedAs: 'Conectado como {who}', + hint: '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.', + }, + nanProbe: { + title: 'Probar la API de NaN con la key', + checking: 'Consultando…', + run: 'Ver respuestas de /v1/models y /v1/usage', + }, + nineRouterConnect: { + connectedSummary: '9Router conectado · volver a conectar', + connectSummary: 'Conectar con el dashboard', + placeholderConfigured: '•••••••• (configurada)', + placeholderPassword: 'Contraseña del dashboard', + saveAndTest: 'Guardar y probar', + removeTitle: 'Desconectar 9Router', + remove: 'Quitar', + disconnectedMsg: '9Router desconectado', + connectedMsg: 'Conectado', + hint: 'La contraseña del dashboard de 9Router (por defecto «123456» hasta que la cambies). Se guarda en el almacén seguro del sistema.', + }, + codexInstall: { + loadingBold: 'Comprobando Codex CLI…', + loadingRest: 'Quorb solo consulta el estado del equipo.', + readyBold: 'Falta Codex CLI en este equipo.', + readyRest: 'Quorb puede instalarlo desde aquí: Windows pedirá confirmación antes de instalar nada.', + installingBold: 'Instalando Codex CLI…', + installingRest: 'Confirma en la ventana de Windows si aparece. Puedes cancelar desde ahí.', + installedBold: 'Codex CLI instalado.', + installedRest: 'La instalación no crea la sesión: usa «Iniciar sesión» cuando quieras.', + installedStaleBold: 'Codex está instalado; revisa tu comando de login.', + installedStaleRest: 'Quorb ya detecta el CLI en este equipo.', + cancelledBold: 'Instalación cancelada.', + cancelledRest: 'No se cambió nada. Puedes intentarlo de nuevo.', + busyBold: 'El instalador está trabajando en otra ventana.', + busyRest: 'Espera a que termine; Quorb lo detecta solo.', + unsupportedBold: 'Este sistema no permite la instalación asistida.', + unsupportedRest: 'Está pensada para Windows; puedes instalarlo a mano y Quorb lo detectará.', + noNodeBold: 'No se detecta Node.js y npm.', + noNodeRest: 'Quorb solo comprueba que existan; la versión (Node.js 22 o superior) la valida el instalador al confirmar. Instálalos y vuelve a comprobar.', + errorReadBold: 'No se pudo consultar el instalador.', + errorReadRest: 'Vuelve a comprobar el estado.', + errorInstallBold: 'No se pudo instalar Codex CLI.', + errorInstallRest: 'Puedes reintentarlo.', + install: 'Instalar Codex', + installing: 'Instalando…', + retry: 'Reintentar', + recheck: 'Volver a comprobar', + copy: 'Copiar orden', + detailsSummary: 'Qué instala exactamente', + detailsPinnedBefore: 'Paquete fijado: ', + detailsPinnedAfter: ' desde el registro oficial de npm. No se instala «la última».', + detailsFolderBefore: 'Carpeta de destino: ', + detailsFolderAfter: ', dentro de la carpeta de Quorb. No se toca el npm global ni las variables de entorno.', + detailsScripts: 'Se ignoran los scripts de ciclo de vida del paquete y se valida la versión real antes de darlo por bueno.', + detailsNode: 'Antes de instalar, Quorb solo comprueba que Node.js y npm existan (sin ejecutarlos); la versión mínima se valida al confirmar la instalación. No se instala Node ni se piden permisos de administrador.', + }, // 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 @@ -387,6 +454,62 @@ export const es = { 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.', }, + // Paneles de APIs y conexiones (T2c slice 2 C2, #66). «Base URL», los + // rótulos de consolas de terceros y las marcas no se traducen. + createApiKeyIn: 'Créala en', + openrouter: { + kicker: 'Pago por uso · API oficial', + note: 'Gasto del mes y de hoy, límite de la key (si tiene) y saldo de la cuenta.', + }, + nan: { + kicker: 'Suscripción · cuenta o API key', + noteBefore: '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', + noteAfter: 'y las cuotas publicadas.', + stepTitle: 'Conecta tu cuenta o pega una API key', + advancedSummary: 'Avanzado · URL, facturación y endpoint de consumo', + billingDayLabel: 'Día de inicio de facturación', + billingDayHint: 'Solo para GLM 5.3 (cuenta por periodo de Stripe) cuando usas la API key.', + usageEndpointLabel: 'Endpoint de consumo · URL', + usageEndpointHint: 'Se llama con tu key como Bearer y debe devolver JSON.', + pointerUsedLabel: 'JSON Pointer · usado', + pointerLimitLabel: 'JSON Pointer · límite', + pointerUsedHint: 'p. ej. /usage/used_tokens', + pointerLimitHint: 'p. ej. /usage/quota_tokens', + usageLabelField: 'Etiqueta', + usageLabelPlaceholder: 'Consumo', + }, + nineRouter: { + kicker: 'Pasarela local · uso agregado', + noteBefore: 'Quorb lee el uso y el gasto que', + noteAfter: '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.', + stepsAria: 'Cómo conectar 9Router', + connectTitle: 'Conecta tu dashboard local', + periodTitle: 'Periodo y desglose', + periodLabel: 'Periodo de uso', + periodHint: 'Rango que muestra el orbe y su historial.', + periodToday: 'Hoy', + period24h: 'Últimas 24 h', + period7d: 'Últimos 7 días', + period30d: 'Últimos 30 días', + period60d: 'Últimos 60 días', + periodAll: 'Todo', + subprovidersToggle: 'Mostrar cada sub-proveedor como su propia cuenta', + minSpendLabel: 'Gasto mínimo por sub-proveedor (USD)', + minSpendHint: 'No muestra un sub-orbe hasta que su gasto en el periodo supere esta cifra. 0 = cualquiera que gaste.', + budgetLabel: 'Presupuesto (USD)', + budgetHint: 'Opcional: pinta la barra de gasto del periodo frente al tope que pongas.', + advancedSummary: 'Avanzado · dirección del servidor', + baseUrlHint: 'Dónde escucha 9Router (por defecto http://localhost:20128).', + hiddenTitle: 'Suborbes ocultos de 9Router', + hiddenShow: 'mostrar', + hiddenHint: 'Los ocultaste desde su panel en la tira. Pulsa para volver a mostrarlos.', + }, + adminApi: { + kicker: 'Pago por uso · Admin key', + showSpend: 'Mostrar gasto del mes', + anthropicHintBefore: 'Las APIs de coste no admiten OAuth: hace falta una', + anthropicHintAfter: '(solo lectura de la organización).', + }, }, }, }, diff --git a/src/lib/settings-visibility.test.ts b/src/lib/settings-visibility.test.ts index e7015c3..7bea00b 100644 --- a/src/lib/settings-visibility.test.ts +++ b/src/lib/settings-visibility.test.ts @@ -1,5 +1,6 @@ import source from '../Settings.tsx?raw'; import types from './types.ts?raw'; +import { es } from '../i18n/es'; import { describe, expect, it } from 'vitest'; import { CONFIG_KEY, configKeyFor, isAccountVisible, setAccountVisibility } from './settings-visibility'; import type { AppConfig } from './types'; @@ -16,13 +17,17 @@ describe('obsolete hidden-circles block (#98)', () => { expect(source.includes('Ninguno. Para ocultar uno')).toBe(false); expect(source.includes('cfg.hiddenProviders.map')).toBe(false); expect(types).toContain('hiddenProviders: string[]'); - expect(source.includes('label="Mostrar en el widget"')).toBe(true); + expect(source.includes('label="Mostrar en el widget"')).toBe(false); + // El texto vive en el catálogo (patrón destructive-buttons): se exige el + // cableado por clave en Settings.tsx y el valor verbatim en el catálogo es. + expect(es.settings.providers.showInWidget).toBe('Mostrar en el widget'); + expect(source.includes("label={t(locale, 'settings.providers.showInWidget')}")).toBe(true); }); it('routes every "Mostrar en el widget" toggle through the visibility helpers', () => { // Un toggle con `set(...)` directo no saca el id de `hiddenProviders`: un // círculo ocultado desde el widget quedaría marcado e irrecuperable (#182). - const toggles = [...source.matchAll(//g)].map((m) => m[0]); + const toggles = [...source.matchAll(//g)].map((m) => m[0]); expect(toggles.length).toBeGreaterThan(0); for (const t of toggles) expect(t).toMatch(/onChange=\{(onVisibility|\(v\) => setVisibility\()/); expect(source).toContain("checked={isAccountVisible(cfg, 'zai', cfg.zai.enabled)} onChange={(v) => setVisibility('zai', v)}");