Skip to content

feat(i18n): ids estables de medidores entre Rust y JS (L2 #66) - #159

Merged
egdev6 merged 3 commits into
egdev6:mainfrom
danielgap:feat/i18n-catalogs
Oct 21, 2026
Merged

egdev6 merged 3 commits into
egdev6:mainfrom
danielgap:feat/i18n-catalogs

Conversation

@danielgap

@danielgap danielgap commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Related issue / Issue relacionada

Refs #66

ODD: fase L2, primer paso: ids estables Rust→JS, prerrequisito marcado en la aprobación de L0 (#66 (comment)). L1 entró en #152.

Scope / Alcance

Los meters llevan ahora un id estable además de su label, y la lógica de JS deja de comparar textos en español. No cambia nada visible: mismas etiquetas, etiquetas cortas, colores y alertas.

Rust: Meter.id (model.rs) se fija en los 22 constructores de providers/*.rs:

Proveedor Ids
Claude session, weekly, weekly_opus, weekly_sonnet, weekly_apps, extra_usage
Codex session, weekly, window_days, window_short, window_long, credits
Gemini, NaN, NaN Cloud (modelos) model:<id>
Copilot premium_requests
Cursor plan_total, plan_auto, plan_api
OpenRouter key_limit_daily, key_limit_weekly, key_limit_monthly, key_limit, balance_spent
Anthropic API / OpenAI API budget_monthly
NaN (ventana y personalizado) rate_requests, rate_tokens, custom
9Router spend_period, tokens, tokens_period

En Codex, si las dos ventanas coinciden, la segunda recibe un sufijo determinista (session_18000).

JS, cada sitio de lógica que comparaba texto:

Sitio Ahora
format.ts shortLabel Busca por id. Si el id está vacío o es desconocido, vuelve a la etiqueta.
alerts.ts (clave de memoria y diff entre reportes) Usa el id. Un meter con otra etiqueta pero el mismo id ya no produce alertas fantasma ni perdidas.
meter-colors.ts Decide por id cuando falta windowSeconds. La regex sobre semana/sesión queda solo para ids vacíos.
ProviderDetails.tsx (historial) y key= en ProviderCard/ProviderDetails Usan id, con la etiqueta como respaldo.

Datos persistidos, sin pérdida:

  • history.json: las muestras nuevas guardan el id. Las antiguas, con etiqueta, siguen encontrándose (id || label) hasta que caducan a los 30 días.
  • alertMemory (localStorage): se respeta la clave antigua basada en la etiqueta, así que ninguna alerta de "superado" se repite al actualizar.

Fuera de alcance:

  • message, plan, periodLabel, unit y las notas de get_wm_info. Solo se muestran, ningún código los compara; irán en L4.
  • Catálogos y selector, que van en el siguiente PR de L2/L3.

docs/i18n-inventory.md se regenera porque cambian líneas en src/. Si #158 (CI de #155) entra antes, actualizo esta rama y lo regenero.

Coordination / Coordinación

Contribuidor: @danielgap

Acceptance and validation / Aceptación y validación

  • Criterios cumplidos: ids estables como base de los catálogos (gate de L2 de feat: interfaz bilingüe ES/EN con español por defecto #66: "Tests de claves/fallback/sincronización…").
  • Comprobaciones ejecutadas (Windows 10):
    • Tests primero: los 8 tests nuevos de TS fallaron antes de implementar. En Rust, los tests nuevos no compilaban mientras no existía Meter.id.
    • cargo test: 238/238.
    • npx vitest run --dir src: 620/620.
    • npx tsc --noEmit: OK.
    • node scripts/i18n-inventory.mjs --check: exit 0.
  • Comprobaciones no ejecutadas: no hay test de id para Anthropic API, OpenAI API ni Copilot, porque sus meters se construyen dentro del fetch asíncrono. Los ids están fijados y compilan. No hay prueba manual: no hay cambio visible.
  • Bloqueos pendientes: ninguno.

…v6#66)

Meter labels are Spanish text that JS logic matched on: short ring labels,
alert dedupe and reset diffing, ring colors, history lookup and React
keys. Translating them would silently break all of that.

Add `Meter.id` (session, weekly, credits, model:<id>, ...) at every
provider construction site and switch that logic to the id. Labels stay
as today, so nothing visible changes. History samples now store the id
and still match legacy label samples until they age out; stored alert
memory keyed by label is honored so no alert repeats after upgrading.
Copilot AI balanced review requested due to automatic review settings October 20, 2026 21:44

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@egdev6

egdev6 commented Oct 21, 2026

Copy link
Copy Markdown
Owner

Gracias @danielgap, review hecho — muy sólida, nada bloqueante y lista para merge. Solo dejo estas observaciones menores como seguimiento:

  1. Throttle en medidores sin id (src-tauri/src/history.rs): el throttle va por id || label; si un proveedor futuro olvida fijar id, una etiqueta traducida duplicaría puntos del historial. Hoy no ocurre (los 22 constructores ya tienen id), pero conviene que el PR de catálogos (L2/L3) lo deje explícito o lo haga imposible.

  2. Sin tests de id en anthropic_api, openai_api ni copilot (lo declara la propia PR, porque los meters se construyen dentro del fetch async). Para el siguiente PR: extraer la construcción del meter a una función testeable sería barato.

  3. alerts.ts legacyKey: se calcula siempre, incluso cuando meterKey ya es la etiqueta (id vacío), con un lookup duplicado idéntico. Inofensivo, solo cosmético.

  4. Ids model:<name> con dos puntos: válidos en claves de localStorage (alertMemory) y React keys; solo anticiparlo si estos ids acaban alimentando selectores CSS o catálogos.

  5. Continuidad de historial en Codex: si la API cambia los segundos de ventana, el id con sufijo (session_18000) cambia y la serie del historial se reinicia. Determinista y aceptable, pero conviene tenerlo anotado.

Ninguna de las cinco bloquea: recomiendo fusionar y llevar los puntos 1–2 al PR de catálogos.

@egdev6

egdev6 commented Oct 21, 2026

Copy link
Copy Markdown
Owner

@danielgap el check de Windows fallaba porque tras el merge de main (d6927fc) la cabecera del inventario quedó con los hashes de main en vez de los del árbol fusionado (el cuerpo ya coincidía; solo la cabecera difería — exactamente el efecto merge-ref documentado en #158).

Resuelto en 68df5c3 (push a tu rama con maintainerCanModify, como owner): regenerado con node scripts/i18n-inventory.mjs --out docs/i18n-inventory.md, --check verificado en verde localmente antes de empujar. Diff mínimo: 1 línea (la cabecera). El resto de la PR no se toca.

@egdev6
egdev6 merged commit 749a051 into egdev6:main Oct 21, 2026
5 checks passed
egdev6 added a commit that referenced this pull request Oct 21, 2026
…s nativos (L2/L3 #66) (#163)

* feat(i18n): switch tray, window titles and Codex dialog by locale (#66)

Add src-tauri/src/i18n.rs: a Locale parsed from the config (unknown -> es)
and a Msg enum with an exhaustive es/en match. Spanish texts are the
existing literals verbatim. The tray menu and the settings, detail and
overlay window titles are rebuilt when a saved config changes locale,
and use the configured locale at startup; the Codex consent dialog reads
it when shown.

Maintainer follow-ups from #159: Meter.id is documented as required and
history asserts it in debug builds (legacy label fallback kept in a pure
sample_key); the meters built inside anthropic_api, openai_api and
copilot fetches are now pure, unit-tested constructors.

* feat(i18n): typed es/en catalogs and language selector in Settings (#66)

Add src/i18n: `es` built from the existing Spanish literals, `en` checked
with `satisfies` so a missing or extra key fails to compile, `t()` with
typed keys, {placeholder} interpolation and a locale -> es -> key
fallback, a closed Intl locale map, and runtime tests for key presence
and placeholder parity.

Settings gets a "Idioma" selector in Apariencia that saves through the
existing queue and broadcasts config-changed; the draft adopts the new
locale so "Guardar cambios" cannot revert it. Settings and DetailPanel
follow the locale with useLocale(); the Settings navigation and page
titles are the first texts migrated.

* fix(i18n): fictional channel fixture and draft reconcile on failed locale save (#66)

- Use the fictional Quorb Beta channel/folder in the Rust catalog test
  instead of the retired Dev identity, so the dev-retirement guard passes
  without being relaxed (P1, review of #163).
- changeLocale reverts the Settings draft to the persisted backend locale
  when the immediate save fails and rethrows; Settings shows the error next
  to the language selector instead of console-only logging (P2, review of #163).
- Cover the reconciliation and the double-failure rethrow with unit tests
  and add settings.language.error to the es/en catalogs.

* docs(i18n): regenerate inventory after merge with main (#66)

---------

Co-authored-by: Quique <egdev6@gmail.com>
Co-authored-by: Enrique Gómez Navarro <egdev6@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants