Repository navigation
feat(i18n): catálogos es/en y selector de idioma con bandeja y títulos nativos (L2/L3 #66) - #163
Conversation
…dev6#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 egdev6#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.
…gdev6#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.
|
Gracias, @danielgap. He revisado
Validación: 651 tests frontend, TypeScript, build y 7 tests aislados del catálogo Rust pasan. La suite de scripts pasa 220/221, con el guard anterior como único fallo. No ejecuté |
…cale save (egdev6#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 egdev6#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 egdev6#163). - Cover the reconciliation and the double-failure rethrow with unit tests and add settings.language.error to the es/en catalogs.
|
¡Gracias por la revisión tan completa! Ambos puntos quedaron resueltos en 7bc6ab3.
Verifiqué además la suite completa en local: vitest 653/653, |
egdev6
left a comment
There was a problem hiding this comment.
Verificado en local sobre 7bc6ab3 (rama actualizada a pr-163):
- P1 ✓ — Los fixtures del test de Rust usan ahora «Quorb Beta» /
.quorb-beta(identidad ficticia, no la retirada Dev) y el guard no se relaja:node --test scripts/dev-retirement.test.mjs5/5 ycargo test i18n7/7 en verde. - P2 ✓ —
changeLocalerelee el locale persistido del backend al fallar el guardado, devuelve el borrador a ese locale conservando el resto de ediciones y relanza el error; Ajustes lo muestra junto al selector consettings.language.erroren ambos catálogos. Los dos tests nuevos deuse-locale.test.tscubren la reconciliación y el rethrow del doble fallo.tsc --noEmitlimpio y suite completa verde (653/653 antes del merge).
Un detalle nuevo tras el merge con main (e2cf99b): docs/i18n-inventory.md quedó desactualizado — node scripts/i18n-inventory.mjs --check sale con código 1 en local (los tree hashes del encabezado no coinciden con los árboles del merge) y de hecho el job Tests and build (Windows) de la CI falló justo en ese paso. Basta regenerarlo en la rama:
node scripts/i18n-inventory.mjs --out docs/i18n-inventory.md
Con ese commit el PR quedaría listo para aprobar por mi parte.
|
Regenerado |
Atiende la review de egdev6#169 y los puntos 2–5 acordados en egdev6#168. Integra `main` (ya con egdev6#163) y corrige el diff anterior: - `createWidgetRecovery` (egdev6#148) queda acotado a Windows. En `xdg-shell` el compositor, por diseño, no expone la posición absoluta de una ventana cliente y `outerPosition()` devuelve (0, 0): el ciclo de 5 s concluía que la ventana estaba descolocada y la reubicaba. - El muestreo de presencia (egdev6#154) queda acotado a macOS. El PR asumía que en Wayland `cursorPosition()` fallaría; en WebKitGTK no falla, devuelve (0, 0), así que cada muestra concluía «cursor fuera» y cerraba el panel al instante. Linux vuelve a cerrar por los eventos del DOM. - El asa arrancaba su aritmética por delta desde `outerPosition()`, que en Wayland devuelve (0, 0). Ahora arranca desde `widgetBounds.current`, que es la verdad incluso en el caso legítimo (0, 0) porque lo escribe el propio `setWidgetBounds` que colocó la ventana, no una lectura del compositor. Queda documentado en el código. Al soltar, el imán usa el desplazamiento acumulado cuando el compositor no reporta posición. - `OPEN_DELAY` pasa a 350 ms solo fuera de Windows y macOS. En Linux el barrido del puntero no tiene más red de contención que el temporizador, porque el muestreo nativo es solo de macOS; en Windows y macOS los 90 ms originales se conservan y egdev6#164 ya mitiga la reapertura por foco. El toggle por clic sale del PR y se propone como issue de producto aparte. - `styles.css` declara el fondo transparente también en `#root` y `.dock`. El refactor `:is(...)` y el scoping `data-auto-hide` quedan revertidos: cambiaban la ocultación con auto-ocultar desactivado en cualquier plataforma y son fuera de alcance. `tauri.conf.json` y `ProviderGauge.tsx` vuelven a quedar idénticos a `main`. `noRedirectionBitmap` es opción solo de Windows (en `tao 0.37.1` hay cuatro referencias, todas en `platform_impl/windows/`, y ninguna en `linux/` ni `wayland/`), y no puede trasladarse a `tauri.windows.conf.json`: `tauri-utils` fusiona la config de plataforma con JSON Merge Patch (RFC 7396), que reemplaza los arrays completos, así que un `app.windows` en el override se tragaría la definición entera de la ventana en Windows.
…rrastre en Wayland (#168) (#169) * fix(linux): resolve wayland transparency, drag movement and hover regressions (#107) Quorb v0.7.0 introduced multiplatform improvements (#143, #148, #154) that inadvertently broke Linux/Wayland due to differences in desktop protocols and WebKitGTK: - Window transparency: `noRedirectionBitmap: true` in tauri.conf.json bypassed compositing redirection buffers, rendering the widget as an opaque black box on Wayland compositors (KDE KWin, Mutter). - Widget disappearing / flickering: Windows DWM recovery loop (#148) queried `outerPosition()`, which Wayland xdg-shell intentionally reports as (0, 0), causing infinite repositioning loops. Guarded with `/Windows/.test(navigator.userAgent)`. - Premature popup closing: Detail presence polling (#154) queried `cursorPosition()`, which on Linux/GTK returns (0, 0) instead of throwing. Comparing (0, 0) against window geometry triggered immediate close timers every 400ms. Guarded with `/Macintosh/.test(navigator.userAgent)`. - Drag movement freeze: Drag start now initializes synchronously from `widgetBounds.current` and calculates move deltas from pointer offsets, avoiding freezes when window coordinates report (0, 0). - Hover ergonomics: Raised `OPEN_DELAY` from 90ms to 350ms to prevent accidental triggers, and enabled direct click-to-open/toggle on provider gauges. * fix(linux): acotar recoveries por plataforma y desbloquear el arrastre Atiende la review de #169 y los puntos 2–5 acordados en #168. Integra `main` (ya con #163) y corrige el diff anterior: - `createWidgetRecovery` (#148) queda acotado a Windows. En `xdg-shell` el compositor, por diseño, no expone la posición absoluta de una ventana cliente y `outerPosition()` devuelve (0, 0): el ciclo de 5 s concluía que la ventana estaba descolocada y la reubicaba. - El muestreo de presencia (#154) queda acotado a macOS. El PR asumía que en Wayland `cursorPosition()` fallaría; en WebKitGTK no falla, devuelve (0, 0), así que cada muestra concluía «cursor fuera» y cerraba el panel al instante. Linux vuelve a cerrar por los eventos del DOM. - El asa arrancaba su aritmética por delta desde `outerPosition()`, que en Wayland devuelve (0, 0). Ahora arranca desde `widgetBounds.current`, que es la verdad incluso en el caso legítimo (0, 0) porque lo escribe el propio `setWidgetBounds` que colocó la ventana, no una lectura del compositor. Queda documentado en el código. Al soltar, el imán usa el desplazamiento acumulado cuando el compositor no reporta posición. - `OPEN_DELAY` pasa a 350 ms solo fuera de Windows y macOS. En Linux el barrido del puntero no tiene más red de contención que el temporizador, porque el muestreo nativo es solo de macOS; en Windows y macOS los 90 ms originales se conservan y #164 ya mitiga la reapertura por foco. El toggle por clic sale del PR y se propone como issue de producto aparte. - `styles.css` declara el fondo transparente también en `#root` y `.dock`. El refactor `:is(...)` y el scoping `data-auto-hide` quedan revertidos: cambiaban la ocultación con auto-ocultar desactivado en cualquier plataforma y son fuera de alcance. `tauri.conf.json` y `ProviderGauge.tsx` vuelven a quedar idénticos a `main`. `noRedirectionBitmap` es opción solo de Windows (en `tao 0.37.1` hay cuatro referencias, todas en `platform_impl/windows/`, y ninguna en `linux/` ni `wayland/`), y no puede trasladarse a `tauri.windows.conf.json`: `tauri-utils` fusiona la config de plataforma con JSON Merge Patch (RFC 7396), que reemplaza los arrays completos, así que un `app.windows` en el override se tragaría la definición entera de la ventana en Windows. * docs(i18n): regenerar el inventario sobre el árbol ya mergeado El header graba `git rev-parse HEAD:src`; generado antes del commit, no coincidía con el árbol resultante y el gate de Windows lo rechazaba. * refactor(frontend): one tested osFromUA helper and two named platform gates The three inline UA regexes in Widget.tsx (OPEN_DELAY, the DWM recovery audit of #148, the cursor presence sampling of #154) are now a single injected-UA helper, and each gate is a named, tested predicate next to the logic it guards. Behaviour is unchanged: the helpers keep the exact predicates they replace, and a regression test pins each one against the legacy regex it replaced. Follows the review of #169 (suggestions 1 and 2). * docs(frontend): annotate the three !important and the (0,0) drag branch Records the two notes the #169 review asked to keep with the code: the three `!important` that make the transparent widget work under WebKitGTK are the first candidates to remove if the cascade is ever refactored, and a release from a real (0,0) corner takes the delta branch whose only possible deviation is compositor rounding. Regenerates docs/i18n-inventory.md over the merged tree (#169 review, point 4). * fix(i18n): regenerate the text inventory over the committed tree The previous regeneration ran before the commit that changed src/, so the header recorded the previous commit's src tree hash and the gate failed on the Windows job. The file is the only thing this commit changes, so the header stays valid. Verified with core.autocrlf=true to match the Windows checkout.
Related issue / Issue relacionada
Refs #66
ODD: fase L2/L3, núcleo: catálogos y selector global. Sigue a #152 (L1) y #159 (ids estables). Gate de #66: "Tests de claves/fallback/sincronización y selección persistente".
Scope / Alcance
El idioma se elige en Ajustes → Apariencia → Idioma (Español / English). Se guarda al momento y cambia sin reiniciar:
El resto de textos se migra por áreas en los siguientes PRs (T2c). Con el idioma en
es, todo se ve igual que hoy.Son dos commits revisables por separado:
1. Rust:
feat(i18n): switch tray, window titles and Codex dialog by localesrc-tauri/src/i18n.rs:Locale(cualquier valor desconocido se trata comoes) yenum Msgconmatchexhaustivo es/en. Los textos en español son los literales actuales, copiados tal cual, y un test lo comprueba.save_configypatch_configreconstruyen la bandeja (set_menu, mismos ids y handlers) y re-titulan las ventanas abiertas (set_title). Al arrancar y al crear ventanas se usa el idioma configurado.locale_relabel, y se ejecuta con efectos inyectados (run_locale_relabel). Así se testea sin Tauri.Meter.idqueda documentado como obligatorio. El historial hacedebug_assert!si falta, y el fallback a la etiqueta para datos antiguos vive en una función pura,sample_key.anthropic_api,openai_apiycopilotsalen de funciones puras con test (budget_meter,premium_meter).2. TS:
feat(i18n): typed es/en catalogs and language selector in Settingssrc/i18n/es.ts: hecho con los literales existentes, sin retraducir (matiz 4 de L0).en.tsusasatisfies Messages, así que una clave que falte o sobre no compila.t(locale, key, params?)acepta solo claves válidas, interpola{placeholder}y, si falta un texto, cae así: locale → es → la propia clave.intlLocalees un mapa cerrado es→es-ES, en→en-US.useLocale()(Ajustes, detalle): lee la config y escuchaconfig-changed. Vuelve a leer la config, porque el payload a veces es(). El widget usa su propia config, que ya recibe ese evento.docs/i18n-inventory.mdregenerado.Fuera de alcance: el resto de textos de React y Rust (T2c), los formatos y los errores propios (L4), y
message/plan/periodLabelde los proveedores (L4).Tamaño: unas 1050 líneas escritas con los tests (Rust ~550, TS ~560), más el inventario generado. Lo mantengo en un PR porque el cambio de idioma solo se puede probar completo con las dos partes. Si prefieres revisarlo en dos, lo separo.
Coordination / Coordinación
Contribuidor: @danielgap
Acceptance and validation / Aceptación y validación
i18n, del historial y de los medidores extraídos fallaron antes de implementar. Los delocale_relabelno compilaban (E0425) antes de existir la función.changeLocalefalló con "is not a function".cargo test: 251/251.npx vitest run --dir src: 651/651.npx tsc --noEmit: OK.node scripts/i18n-inventory.mjs --check: OK.npm run app:dev. Al cambiar a English:MockRuntime::set_titleno hace nada ytitle()devuelve"", ymock_contextarranca sin bandeja. Por eso se testea la decisión, y lo pintado se probó a mano.onChangedel<select>no tiene test de componente, porque el repo no usa jsdom ni testing-library. La lógica está enchangeLocale, que es pura y tiene test. Si te parece bien añadir esas devDependencies, puedo proponerlo aparte.