El error de i18n en aria-label que estaba escondido a plena vista
@vdaluz/astro-blog tiene una capa de i18n en/es desde hace un tiempo. Cada cadena visible en PostCard, Pagination, BlogPostMeta pasa por una búsqueda t(locale). Eso daba bastante orgullo al momento de construirlo. Por eso fue desalentador encontrar dos aria-labels que nunca se enteraron.
Dónde estaban escondidos
Los botones de flecha individuales de Pagination.astro (primero, anterior, siguiente, último) llaman correctamente a strings.goToFirstPage y sus equivalentes. Pero el <nav> exterior que envuelve todo el bloque de paginación tenía:
<nav class="..." aria-label="Blog pagination">
Un literal de texto plano, sentado una línea por encima de código que ya hacía lo correcto tres veces distintas. TagFilterNav.astro tenía la misma forma: una prop locale nunca formó parte de su interfaz, así que su aria-label por defecto era simplemente 'Filter posts', punto.
En una página en inglés nadie lo nota. En un índice de blog en español, un lector de pantalla anuncia “Blog pagination” en inglés dentro de una página que, por lo demás, está en español. Es pequeño, pero es exactamente el tipo de detalle pequeño que erosiona la confianza en una traducción.
Por qué la corrección parcial es mejor camuflaje
Lo que más llamó la atención fue lo plausible que se veía el error. Al hacer grep de aria-label en Pagination.astro aparecen cuatro sitios de llamada correctos seguidos. El quinto, el envoltorio del nav, se mezcla perfectamente a menos que se esté revisando cada uno específicamente contra el archivo de i18n.
Un componente que está 100% codificado de forma fija se descubre la primera vez que alguien carga el sitio en español. Un componente que está 90% correcto pasa una revisión superficial y sobrevive hasta que alguien lee cada línea contra la fuente de verdad, la misma razón por la que una prop de subtítulo que mentía sobre su color pasó desapercibida en seis sitios de llamada.
La solución
Dos cadenas nuevas en i18n.ts (blogPagination y filterPosts, con sus equivalentes en español). El nav de Pagination.astro ahora usa aria-label={strings.blogPagination}. TagFilterNav.astro recibió una prop locale real con valor por defecto 'en', con el aria-label por defecto recurriendo a strings.filterPosts, pero solo cuando quien lo consume no pasa un ariaLabel explícito que lo sobrescriba, ya que algunos sitios ya traducen esa etiqueta por su cuenta a nivel de aplicación.
Verificándolo sin un paso de compilación
astro-blog distribuye código fuente crudo sin paso de compilación, así que se pudo apuntar directamente al error: superponer la corrección local sobre la copia instalada en un sitio consumidor, correr el servidor de desarrollo de ese sitio, cargar la página real /es/blog en un navegador. La etiqueta del árbol de accesibilidad del nav decía “Paginación del blog.” No una prueba de snapshot, la página real renderizada.
Eso también sacó a la luz algo útil. El sitio contra el que se probó ya le pasa su propio ariaLabel explícito a TagFilterNav, así que el nuevo valor por defecto nunca se activa en esa página. Lo cual es correcto, se supone que la sobrescritura debe ganar, pero significa que solo se demostró que el camino de la sobrescritura sigue funcionando, no el nuevo valor por defecto. La cadena por defecto está cubierta por una prueba unitaria, no por una página en vivo. Vale la pena ser honesto sobre la diferencia entre esos dos tipos de verificación, en lugar de dejar que una verificación que pasa reemplace la afirmación equivocada.
Qué se revisaría la próxima vez
Antes de lanzar una capa de i18n, conviene hacer grep de cada prop de texto literal en cada componente y compararla contra el archivo de cadenas, no solo las que ya parecen estar usando t(). Un componente parcialmente conectado es un error peor que uno completamente codificado de forma fija, porque es más difícil de detectar.
Lecturas relacionadas
Vaciar un backlog: cinco arreglos pequeños en una sola versión
Un arreglo de accesibilidad para una decoración muerta, un campo de esquema que validaba pero nunca se renderizaba, y una corrida de formateo que cambió en silencio el estilo de comillas en cuatro archivos.
El linter estaba señalando la línea correcta por el motivo equivocado
Un atributo que casi borré por una suposición, un desborde que solo existe en ancho de teléfono, y una suite de accesibilidad que pasó en cada versión de esto, la rota y la arreglada.
El error de escape que solo aparece con un segundo parámetro de consulta
Una función de reescritura que funcionaba en cada prueba y cada URL real que había visto, y aun así se habría roto en el momento en que alguien agregara un segundo parámetro de consulta.