Saltar al contenido
Development

Agregando español y portugués a una app de escritorio, y quedando atrapado en mi propia suposición

Por Victor Da Luz
taurisveltei18ndev-loggreenhouse

Pasé una sesión agregando soporte de idiomas a Greenhouse, mi app de escritorio en Tauri para gestionar proyectos creativos. El issue pedía tres cosas: detectar el idioma del sistema operativo, dejar que el usuario cambie de idioma, y redactar el contenido real en español y portugués sobre la extracción de strings que ya se había hecho antes. En el papel es una funcionalidad pequeña. En la práctica me obligó a tomar dos decisiones que no podía improvisar.

Problema: ¿cómo funciona en realidad un cambio de idioma en un webview?

Paraglide (la librería de i18n) por defecto recarga la página al llamar setLocale(). También ofrece { reload: false }, combinado con cableado de reactividad propio, para que la interfaz se actualice sin refrescar. El movimiento obvio para software “moderno” es saltarse la recarga. Más fluido, sin parpadeo, se siente nativo.

Salvo que no podía señalar nada que probara que ese camino funcionaba en esta app. Las funciones de mensaje de Paraglide son solo funciones planas que leen el idioma actual cuando se llaman, no son reactivas por sí solas. Cablear { reload: false } correctamente significaba pasar un shim de reactividad por cada lugar donde se renderiza un mensaje, y confiar en que el rastreo de señales de grano fino de Svelte se propagaría a través de una cadena de llamadas a funciones planas a las que no se le había dicho específicamente nada.

Por qué este enfoque: la recarga es la opción aburrida y demostrable

Una recarga completa vuelve a correr el arranque de mi app, pero el proceso de Rust detrás de la ventana de Tauri nunca se reinicia, el estado en memoria (qué bóveda está activa, los permisos de sistema de archivos ya otorgados) sobrevive sin problema. Y la app ya tenía un precedente para esto: cambiar de bóveda pide un reinicio por una razón similar. Que un cambio de idioma recargue la página no es un retroceso respecto a ese patrón, es consistente con él.

Así que en vez de asumir que el camino elegante sin recarga funcionaría, construí el aburrido y lo probé de punta a punta con una sesión real de WebDriver manejando la app compilada real, no una prueba simulada de un componente, un clic real en una ventana real:

• clicking Español…
• waiting for the reload to complete and dashboard to re-render…
  boot locale: English pressed=true, Español pressed=false
PASS - locale persisted across a real reload.

Ese es todo el punto de manejar la app real en vez de una prueba de componente simulada: un render con mockIPC puede probar que mi código de Svelte no se cae, pero no puede probar que un comando de Rust en realidad escribió un archivo en disco, o que una recarga real de página en realidad lo vuelve a leer correctamente. Solo lo real prueba lo real.

Percance: mi configuración de pruebas borró un archivo de configuración que no quería tocar

Mientras cableaba ese arnés de WebDriver, necesitaba una bóveda desechable para que la app arrancara directo al panel principal en vez del asistente de incorporación. Encontré el archivo real de configuración de la app en disco y, sin revisar qué había en realidad ahí adentro, lo borré para tener un punto de partida limpio.

No era un punto de partida limpio. Era la configuración real apuntando a mi bóveda de trabajo real. Nada dentro de la bóveda en sí se tocó, el archivo de configuración solo guarda una ruta, no los datos detrás de ella, pero de todas formas fue un momento de “mide dos veces” que me salté. El arreglo fue fácil (volver a adoptar la misma carpeta), pero es el tipo de error que es obvio en retrospectiva e invisible en el momento, porque un archivo sentado en una carpeta de soporte de la app simplemente no se ve valioso de la forma en que se ve el historial de un .git.

Percance: la traducción redactada usó una palabra que yo nunca diría

El otro percance llegó al revisar las traducciones redactadas por máquina para la metáfora de “cantero” de la app, el texto que avisa cuando la cola de proyectos se está quedando corta. El borrador decía “bancales.” Es una palabra real, encajaba en la oración, y se veía bastante plausible en la pantalla. También es una palabra que nunca he dicho en voz alta en mi vida, y he hablado español latinoamericano toda mi vida.

Investigar confirmó la intuición: “bancal” rastrea específicamente al vocabulario de la agricultura en terrazas de España, hasta tiene sus propios sinónimos regionales dentro de España, lo cual suele ser señal de que una palabra es un regionalismo en vez de algo usado en todos los lugares donde se habla español. La palabra que la RAE misma marca como el equivalente latinoamericano es “cantero.” Dos palabras distintas para el mismo objeto, y el borrador se había ido por defecto al hemisferio equivocado sin notarlo.

Por esto exactamente el contenido de traducción se separó en su propio issue en vez de aprobarse sin más junto con el trabajo mecánico de extracción: un borrador que suena plausible puede estar confiadamente equivocado justo en la dimensión de registro y audiencia que ningún build check va a atrapar jamás. Solo alguien que hable el idioma leyendo la oración nota el “nunca había escuchado eso.”

Resultados

Detección de idioma del sistema operativo, una configuración de idioma persistida, y un selector en el pie de página entre inglés, español y portugués. Borradores completos en español y portugués para las 207 strings de la interfaz, revisados y corregidos por audiencia (español latinoamericano, no peninsular). Un cambio de idioma basado en recarga, probado contra la app compilada real en vez de asumido que funcionaría.

Reflexión

Los dos errores de esta sesión parecen sin relación, uno es un desliz de automatización de pruebas, el otro es una elección de palabra, pero comparten una causa raíz: avanzar rápido en algo que se sentía obviamente bien. “Este archivo de configuración es desechable” y “esta es la palabra en español para cantero” fueron ambas suposiciones confiadas que reemplazaron una verificación que nunca se hizo de verdad. Ninguna fue cara de arreglar. Pero barato de arreglar no es lo mismo que gratis.

Lecturas relacionadas

Development

Seis correcciones pequeñas de UX en una sola sesión

Límites de layout para pantallas ultra anchas, timestamps que respetan el idioma, emojis convertidos a SVG, errores descartables, un hero de primer uso, y la palabra que aparecía tres veces en una sola tarjeta.

Leer
Development

Toast y deshacer para la acción de vault de Greenhouse

La primera superficie de feedback transitorio de la app, la regla de aria-live que hace que un toast renderizado condicionalmente quede sin anunciar en silencio, y un prop que queda desactualizado apenas se renombra algo.

Leer